diff options
Diffstat (limited to 'plugins/base/src/main/kotlin')
66 files changed, 134 insertions, 5162 deletions
diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index 8fc46870..6b000ac6 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -3,14 +3,14 @@ package org.jetbrains.dokka.base import org.jetbrains.dokka.CoreExtensions -import org.jetbrains.dokka.analysis.KotlinAnalysis -import org.jetbrains.dokka.analysis.ProjectKotlinAnalysis +import org.jetbrains.dokka.base.generation.SingleModuleGeneration import org.jetbrains.dokka.base.renderers.* import org.jetbrains.dokka.base.renderers.html.* import org.jetbrains.dokka.base.renderers.html.command.consumers.PathToRootConsumer +import org.jetbrains.dokka.base.renderers.html.command.consumers.ReplaceVersionsConsumer import org.jetbrains.dokka.base.renderers.html.command.consumers.ResolveLinkConsumer -import org.jetbrains.dokka.base.resolvers.external.ExternalLocationProviderFactory import org.jetbrains.dokka.base.resolvers.external.DefaultExternalLocationProviderFactory +import org.jetbrains.dokka.base.resolvers.external.ExternalLocationProviderFactory import org.jetbrains.dokka.base.resolvers.external.javadoc.JavadocExternalLocationProviderFactory import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProviderFactory import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory @@ -23,24 +23,13 @@ import org.jetbrains.dokka.base.transformers.pages.annotations.SinceKotlinTransf import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter import org.jetbrains.dokka.base.transformers.pages.merger.* -import org.jetbrains.dokka.base.transformers.pages.samples.DefaultSamplesTransformer import org.jetbrains.dokka.base.transformers.pages.sourcelinks.SourceLinksTransformer -import org.jetbrains.dokka.base.translators.descriptors.DefaultDescriptorToDocumentableTranslator -import org.jetbrains.dokka.base.translators.documentables.DefaultDocumentableToPageTranslator -import org.jetbrains.dokka.base.translators.psi.DefaultPsiToDocumentableTranslator -import org.jetbrains.dokka.base.generation.SingleModuleGeneration -import org.jetbrains.dokka.base.renderers.html.command.consumers.ReplaceVersionsConsumer import org.jetbrains.dokka.base.transformers.pages.tags.CustomTagContentProvider import org.jetbrains.dokka.base.transformers.pages.tags.SinceKotlinTagContentProvider -import org.jetbrains.dokka.base.translators.descriptors.DefaultExternalDocumentablesProvider -import org.jetbrains.dokka.base.translators.descriptors.ExternalClasslikesTranslator -import org.jetbrains.dokka.base.translators.descriptors.ExternalDocumentablesProvider -import org.jetbrains.dokka.base.utils.NoopIntellijLoggerFactory +import org.jetbrains.dokka.base.translators.documentables.DefaultDocumentableToPageTranslator import org.jetbrains.dokka.plugability.DokkaPlugin import org.jetbrains.dokka.plugability.DokkaPluginApiPreview import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement -import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.dokka.renderers.PostAction import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer import org.jetbrains.dokka.transformers.pages.PageTransformer @@ -55,25 +44,16 @@ class DokkaBase : DokkaPlugin() { val externalLocationProviderFactory by extensionPoint<ExternalLocationProviderFactory>() val outputWriter by extensionPoint<OutputWriter>() val htmlPreprocessors by extensionPoint<PageTransformer>() - val kotlinAnalysis by extensionPoint<KotlinAnalysis>() + @Deprecated("It is not used anymore") val tabSortingStrategy by extensionPoint<TabSortingStrategy>() val immediateHtmlCommandConsumer by extensionPoint<ImmediateHtmlCommandConsumer>() - val externalDocumentablesProvider by extensionPoint<ExternalDocumentablesProvider>() - val externalClasslikesTranslator by extensionPoint<ExternalClasslikesTranslator>() + val singleGeneration by extending { CoreExtensions.generation providing ::SingleModuleGeneration } - val descriptorToDocumentableTranslator by extending { - CoreExtensions.sourceToDocumentableTranslator providing ::DefaultDescriptorToDocumentableTranslator - } - - val psiToDocumentableTranslator by extending { - CoreExtensions.sourceToDocumentableTranslator providing ::DefaultPsiToDocumentableTranslator - } - val documentableMerger by extending { CoreExtensions.documentableMerger providing ::DefaultDocumentableMerger } @@ -168,7 +148,9 @@ class DokkaBase : DokkaPlugin() { } val pageMerger by extending { - CoreExtensions.pageTransformer providing ::PageMerger + CoreExtensions.pageTransformer providing ::PageMerger order { + // TODO [beresnev] make last() or at least after samples transformer + } } val sourceSetMerger by extending { @@ -189,15 +171,6 @@ class DokkaBase : DokkaPlugin() { CoreExtensions.renderer providing ::HtmlRenderer } - val defaultKotlinAnalysis by extending { - kotlinAnalysis providing { ctx -> - ProjectKotlinAnalysis( - sourceSets = ctx.configuration.sourceSets, - logger = ctx.logger - ) - } - } - val locationProvider by extending { locationProviderFactory providing ::DokkaLocationProviderFactory } @@ -218,12 +191,6 @@ class DokkaBase : DokkaPlugin() { htmlPreprocessors with RootCreator applyIf { !delayTemplateSubstitution } } - val defaultSamplesTransformer by extending { - CoreExtensions.pageTransformer providing ::DefaultSamplesTransformer order { - before(pageMerger) - } - } - val sourceLinksTransformer by extending { htmlPreprocessors providing ::SourceLinksTransformer order { after(rootCreator) } } @@ -275,26 +242,6 @@ class DokkaBase : DokkaPlugin() { htmlPreprocessors providing ::SearchbarDataInstaller order { after(sourceLinksTransformer) } } - val defaultExternalDocumentablesProvider by extending { - externalDocumentablesProvider providing ::DefaultExternalDocumentablesProvider - } - - val defaultExternalClasslikesTranslator by extending { - externalClasslikesTranslator providing ::DefaultDescriptorToDocumentableTranslator - } - - internal val disposeKotlinAnalysisPostAction by extending { - CoreExtensions.postActions with PostAction { this@DokkaBase.querySingle { kotlinAnalysis }.close() } - } - - private companion object { - init { - // Suppress messages emitted by the IntelliJ logger since - // there's not much the end user can do about it - com.intellij.openapi.diagnostic.Logger.setFactory(NoopIntellijLoggerFactory()) - } - } - @OptIn(DokkaPluginApiPreview::class) override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement diff --git a/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt b/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt deleted file mode 100644 index d49e7c7a..00000000 --- a/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt +++ /dev/null @@ -1,604 +0,0 @@ -package org.jetbrains.dokka.base.parsers - -import com.intellij.psi.PsiElement -import org.intellij.markdown.MarkdownElementTypes -import org.intellij.markdown.MarkdownTokenTypes -import org.intellij.markdown.ast.ASTNode -import org.intellij.markdown.ast.CompositeASTNode -import org.intellij.markdown.ast.LeafASTNode -import org.intellij.markdown.ast.impl.ListItemCompositeNode -import org.intellij.markdown.flavours.gfm.GFMElementTypes -import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor -import org.intellij.markdown.flavours.gfm.GFMTokenTypes -import org.intellij.markdown.html.HtmlGenerator -import org.jetbrains.dokka.base.parsers.factories.DocTagsFromIElementFactory -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.links.PointingToDeclaration -import org.jetbrains.dokka.model.doc.* -import org.jetbrains.dokka.model.doc.Suppress -import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag -import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection -import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag -import java.net.MalformedURLException -import java.net.URL -import org.intellij.markdown.parser.MarkdownParser as IntellijMarkdownParser - -open class MarkdownParser( - private val externalDri: (String) -> DRI?, - private val kdocLocation: String?, -) : Parser() { - - private lateinit var destinationLinksMap: Map<String, String> - private lateinit var text: String - - override fun parseStringToDocNode(extractedString: String): DocTag { - val gfmFlavourDescriptor = GFMFlavourDescriptor() - val markdownAstRoot = IntellijMarkdownParser(gfmFlavourDescriptor).buildMarkdownTreeFromString(extractedString) - destinationLinksMap = getAllDestinationLinks(extractedString, markdownAstRoot).toMap() - text = extractedString - - val parsed = visitNode(markdownAstRoot) - if (parsed.size == 1) { - return parsed.first() - } - return CustomDocTag(children = parsed, params = emptyMap(), name = "") - } - - override fun preparse(text: String) = text.replace("\r\n", "\n").replace("\r", "\n") - - override fun parseTagWithBody(tagName: String, content: String): TagWrapper = - when (tagName) { - "see" -> { - val referencedName = content.substringBefore(' ') - val dri = externalDri(referencedName) - See( - parseStringToDocNode(content.substringAfter(' ')), - dri?.fqDeclarationName() ?: referencedName, - dri - ) - } - "throws", "exception" -> { - val dri = externalDri(content.substringBefore(' ')) - Throws( - parseStringToDocNode(content.substringAfter(' ')), - dri?.fqDeclarationName() ?: content.substringBefore(' '), - dri - ) - } - else -> super.parseTagWithBody(tagName, content) - } - - private fun headersHandler(node: ASTNode) = - DocTagsFromIElementFactory.getInstance( - node.type, - visitNode(node.children.find { it.type == MarkdownTokenTypes.ATX_CONTENT } - ?: throw detailedException("Wrong AST Tree. Header does not contain expected content", node) - ).flatMap { it.children } - ) - - private fun horizontalRulesHandler() = - DocTagsFromIElementFactory.getInstance(MarkdownTokenTypes.HORIZONTAL_RULE) - - private fun emphasisHandler(node: ASTNode) = - DocTagsFromIElementFactory.getInstance( - node.type, - children = node.children.evaluateChildrenWithDroppedEnclosingTokens(1) - ) - - private fun strongHandler(node: ASTNode) = - DocTagsFromIElementFactory.getInstance( - node.type, - children = node.children.evaluateChildrenWithDroppedEnclosingTokens(2) - ) - - private fun List<ASTNode>.evaluateChildrenWithDroppedEnclosingTokens(count: Int) = - drop(count).dropLast(count).evaluateChildren() - - private fun blockquotesHandler(node: ASTNode) = - DocTagsFromIElementFactory.getInstance( - node.type, children = node.children - .filterIsInstance<CompositeASTNode>() - .evaluateChildren() - ) - - private fun listsHandler(node: ASTNode): List<DocTag> { - - val children = node.children.filterIsInstance<ListItemCompositeNode>().flatMap { - if (it.children.last().type in listOf( - MarkdownElementTypes.ORDERED_LIST, - MarkdownElementTypes.UNORDERED_LIST - ) - ) { - val nestedList = it.children.last() - (it.children as MutableList).removeAt(it.children.lastIndex) - listOf(it, nestedList) - } else - listOf(it) - } - - return DocTagsFromIElementFactory.getInstance( - node.type, - children = - children - .flatMap { - if (it.type == MarkdownElementTypes.LIST_ITEM) - DocTagsFromIElementFactory.getInstance( - it.type, - children = it - .children - .filterIsInstance<CompositeASTNode>() - .evaluateChildren() - ) - else - visitNode(it) - }, - params = - if (node.type == MarkdownElementTypes.ORDERED_LIST) { - val listNumberNode = node.children.first().children.first() - mapOf( - "start" to text.substring( - listNumberNode.startOffset, - listNumberNode.endOffset - ).trim().dropLast(1) - ) - } else - emptyMap() - ) - } - - private fun resolveDRI(mdLink: String): DRI? = - mdLink - .removePrefix("[") - .removeSuffix("]") - .let { link -> - try { - URL(link) - null - } catch (e: MalformedURLException) { - externalDri(link) - } - } - - private fun getAllDestinationLinks(text: String, node: ASTNode): List<Pair<String, String>> = - node.children - .filter { it.type == MarkdownElementTypes.LINK_DEFINITION } - .map { - text.substring(it.children[0].startOffset, it.children[0].endOffset).toLowerCase() to - text.substring(it.children[2].startOffset, it.children[2].endOffset) - } + - node.children.filterIsInstance<CompositeASTNode>().flatMap { getAllDestinationLinks(text, it) } - - - private fun referenceLinksHandler(node: ASTNode): List<DocTag> { - val linkLabel = node.children.find { it.type == MarkdownElementTypes.LINK_LABEL } - ?: throw detailedException("Wrong AST Tree. Reference link does not contain link label", node) - val linkText = node.children.findLast { it.type == MarkdownElementTypes.LINK_TEXT } ?: linkLabel - - val linkKey = text.substring(linkLabel.startOffset, linkLabel.endOffset) - - val link = destinationLinksMap[linkKey.toLowerCase()] ?: linkKey - - return linksHandler(linkText, link) - } - - private fun inlineLinksHandler(node: ASTNode): List<DocTag> { - val linkText = node.children.find { it.type == MarkdownElementTypes.LINK_TEXT } - ?: throw detailedException("Wrong AST Tree. Inline link does not contain link text", node) - val linkDestination = node.children.find { it.type == MarkdownElementTypes.LINK_DESTINATION } - val linkTitle = node.children.find { it.type == MarkdownElementTypes.LINK_TITLE } - - // Link destination may be ommited: https://github.github.com/gfm/#example-495 - val link = linkDestination?.let { text.substring(it.startOffset, it.endOffset) } - - return linksHandler(linkText, link, linkTitle) - } - - private fun markdownFileHandler(node: ASTNode) = - DocTagsFromIElementFactory.getInstance( - node.type, - children = node.children - .filterSpacesAndEOL() - .evaluateChildren() - ) - - private fun autoLinksHandler(node: ASTNode): List<DocTag> { - val link = text.substring(node.startOffset + 1, node.endOffset - 1) - - return linksHandler(node, link) - } - - private fun linksHandler(linkText: ASTNode, link: String?, linkTitle: ASTNode? = null): List<DocTag> { - val dri: DRI? = link?.let { resolveDRI(it) } - val linkOrEmpty = link ?: "" - val linkTextString = - if (linkTitle == null) linkOrEmpty else text.substring(linkTitle.startOffset + 1, linkTitle.endOffset - 1) - - val params = if (linkTitle == null) - mapOf("href" to linkOrEmpty) - else - mapOf("href" to linkOrEmpty, "title" to linkTextString) - - return if (link != null && dri == null && !linkOrEmpty.isRemoteLink()) { - DocTagsFromIElementFactory.getInstance( - MarkdownTokenTypes.TEXT, - params = params, - children = linkText.children.drop(1).dropLast(1).evaluateChildren(), - body = linkTextString.removeSurrounding("[", "]") - ) - } else { - DocTagsFromIElementFactory.getInstance( - MarkdownElementTypes.INLINE_LINK, - params = params, - children = linkText.children.drop(1).dropLast(1).evaluateChildren(), - dri = dri - ) - } - } - - private fun codeLineHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance( - MarkdownElementTypes.CODE_BLOCK, - body = text.substring(node.startOffset, node.endOffset) - ) - - private fun textHandler(node: ASTNode, keepAllFormatting: Boolean) = DocTagsFromIElementFactory.getInstance( - MarkdownTokenTypes.TEXT, - body = text.substring(node.startOffset, node.endOffset).transform(), - keepFormatting = keepAllFormatting - ) - - private fun strikeThroughHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance( - node.type, - children = node.children.evaluateChildrenWithDroppedEnclosingTokens(2) - ) - - private fun tableHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance( - GFMElementTypes.TABLE, - children = node.children - .filter { it.type == GFMElementTypes.ROW || it.type == GFMElementTypes.HEADER } - .evaluateChildren() - ) - - private fun headerHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance( - GFMElementTypes.HEADER, - children = node.children - .filter { it.type == GFMTokenTypes.CELL } - .evaluateChildren() - ) - - private fun rowHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance( - GFMElementTypes.ROW, - children = node.children - .filter { it.type == GFMTokenTypes.CELL } - .evaluateChildren() - ) - - private fun cellHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance( - GFMTokenTypes.CELL, - children = node.children.filterTabSeparators().evaluateChildren().trimSurroundingTokensIfText() - ) - - private fun String.isRemoteLink() = try { - URL(this) - true - } catch (e: MalformedURLException) { - false - } - - private fun imagesHandler(node: ASTNode): List<DocTag> = - with(node.children.last().children) { - val destination = find { it.type == MarkdownElementTypes.LINK_DESTINATION } - val description = find { it.type == MarkdownElementTypes.LINK_TEXT } - - val src = destination?.let { - mapOf("href" to text.substring(it.startOffset, it.endOffset)) - } ?: emptyMap() - - val alt = description?.let { - mapOf("alt" to text.substring(it.startOffset + 1, it.endOffset - 1)) - } ?: emptyMap() - - return DocTagsFromIElementFactory.getInstance( - node.type, - params = src + alt - ) - } - - - private fun rawHtmlHandler(node: ASTNode): List<DocTag> = - DocTagsFromIElementFactory.getInstance( - node.type, - body = text.substring(node.startOffset, node.endOffset) - ) - - private fun codeSpansHandler(node: ASTNode) = - DocTagsFromIElementFactory.getInstance( - node.type, - children = DocTagsFromIElementFactory.getInstance( - MarkdownTokenTypes.TEXT, - body = text.substring(node.startOffset + 1, node.endOffset - 1).replace('\n', ' ').trimIndent(), - keepFormatting = true - ) - ) - - private fun codeFencesHandler(node: ASTNode) = - DocTagsFromIElementFactory.getInstance( - node.type, - children = node - .children - .dropWhile { it.type != MarkdownTokenTypes.CODE_FENCE_CONTENT } - .dropLastWhile { it.type != MarkdownTokenTypes.CODE_FENCE_CONTENT } - .filter { it.type != MarkdownTokenTypes.WHITE_SPACE } - .map { - if (it.type == MarkdownTokenTypes.EOL) - LeafASTNode(MarkdownTokenTypes.HARD_LINE_BREAK, 0, 0) - else - it - }.evaluateChildren(keepAllFormatting = true), - params = node - .children - .find { it.type == MarkdownTokenTypes.FENCE_LANG } - ?.let { mapOf("lang" to text.substring(it.startOffset, it.endOffset)) } - ?: emptyMap() - ) - - private fun codeBlocksHandler(node: ASTNode) = - DocTagsFromIElementFactory.getInstance(node.type, children = node.children.mergeLeafASTNodes().flatMap { - DocTagsFromIElementFactory.getInstance( - MarkdownTokenTypes.TEXT, - body = HtmlGenerator.trimIndents(text.substring(it.startOffset, it.endOffset), 4).toString() - ) - }) - - private fun defaultHandler(node: ASTNode) = - DocTagsFromIElementFactory.getInstance( - MarkdownElementTypes.PARAGRAPH, - children = node.children.evaluateChildren() - ) - - private fun visitNode(node: ASTNode, keepAllFormatting: Boolean = false): List<DocTag> = - when (node.type) { - MarkdownElementTypes.ATX_1, - MarkdownElementTypes.ATX_2, - MarkdownElementTypes.ATX_3, - MarkdownElementTypes.ATX_4, - MarkdownElementTypes.ATX_5, - MarkdownElementTypes.ATX_6, - -> headersHandler(node) - MarkdownTokenTypes.HORIZONTAL_RULE -> horizontalRulesHandler() - MarkdownElementTypes.STRONG -> strongHandler(node) - MarkdownElementTypes.EMPH -> emphasisHandler(node) - MarkdownElementTypes.FULL_REFERENCE_LINK, - MarkdownElementTypes.SHORT_REFERENCE_LINK, - -> referenceLinksHandler(node) - MarkdownElementTypes.INLINE_LINK -> inlineLinksHandler(node) - MarkdownElementTypes.AUTOLINK -> autoLinksHandler(node) - MarkdownElementTypes.BLOCK_QUOTE -> blockquotesHandler(node) - MarkdownElementTypes.UNORDERED_LIST, - MarkdownElementTypes.ORDERED_LIST, - -> listsHandler(node) - MarkdownElementTypes.CODE_BLOCK -> codeBlocksHandler(node) - MarkdownElementTypes.CODE_FENCE -> codeFencesHandler(node) - MarkdownElementTypes.CODE_SPAN -> codeSpansHandler(node) - MarkdownElementTypes.IMAGE -> imagesHandler(node) - MarkdownElementTypes.HTML_BLOCK, - MarkdownTokenTypes.HTML_TAG, - MarkdownTokenTypes.HTML_BLOCK_CONTENT, - -> rawHtmlHandler(node) - MarkdownTokenTypes.HARD_LINE_BREAK -> DocTagsFromIElementFactory.getInstance(node.type) - MarkdownTokenTypes.CODE_FENCE_CONTENT, - MarkdownTokenTypes.CODE_LINE, - -> codeLineHandler(node) - MarkdownTokenTypes.TEXT -> textHandler(node, keepAllFormatting) - MarkdownElementTypes.MARKDOWN_FILE -> markdownFileHandler(node) - GFMElementTypes.STRIKETHROUGH -> strikeThroughHandler(node) - GFMElementTypes.TABLE -> tableHandler(node) - GFMElementTypes.HEADER -> headerHandler(node) - GFMElementTypes.ROW -> rowHandler(node) - GFMTokenTypes.CELL -> cellHandler(node) - else -> defaultHandler(node) - } - - private fun List<ASTNode>.filterTabSeparators() = - this.filterNot { it.type == GFMTokenTypes.TABLE_SEPARATOR } - - private fun List<ASTNode>.filterSpacesAndEOL() = - this.filterNot { it.type == MarkdownTokenTypes.WHITE_SPACE || it.type == MarkdownTokenTypes.EOL } - - private fun List<ASTNode>.evaluateChildren(keepAllFormatting: Boolean = false): List<DocTag> = - this.removeUselessTokens().swapImagesThatShouldBeLinks(keepAllFormatting).mergeLeafASTNodes().flatMap { visitNode(it, keepAllFormatting) } - - private fun List<ASTNode>.swapImagesThatShouldBeLinks(keepAllFormatting: Boolean): List<ASTNode> = - if (keepAllFormatting) { - this - } else { - flatMap { node -> - if (node.type == MarkdownElementTypes.IMAGE - && node.children.firstOrNull()?.let { it is LeafASTNode && it.type.name == "!" } == true - && node.children.lastOrNull()?.type == MarkdownElementTypes.SHORT_REFERENCE_LINK - ) { - node.children - } else { - listOf(node) - } - } - } - - private fun List<ASTNode>.removeUselessTokens(): List<ASTNode> = - this.filterIndexed { index, node -> - !(node.type == MarkdownElementTypes.LINK_DEFINITION || ( - node.type == MarkdownTokenTypes.EOL && - this.getOrNull(index - 1)?.type == MarkdownTokenTypes.HARD_LINE_BREAK - )) - } - - private fun List<DocTag>.trimSurroundingTokensIfText() = mapIndexed { index, elem -> - val elemTransformed = if (index == 0 && elem is Text) elem.copy(elem.body.trimStart()) else elem - if (index == lastIndex && elemTransformed is Text) elemTransformed.copy(elemTransformed.body.trimEnd()) else elemTransformed - } - - private val notLeafNodes = listOf( - MarkdownTokenTypes.HORIZONTAL_RULE, - MarkdownTokenTypes.HARD_LINE_BREAK, - MarkdownTokenTypes.HTML_TAG, - MarkdownTokenTypes.HTML_BLOCK_CONTENT - ) - - private fun ASTNode.isNotLeaf() = this is CompositeASTNode || this.type in notLeafNodes - - private fun List<ASTNode>.isNotLeaf(index: Int): Boolean = - if (index in 0..this.lastIndex) - this[index].isNotLeaf() - else - false - - private fun List<ASTNode>.mergeLeafASTNodes(): List<ASTNode> { - val children: MutableList<ASTNode> = mutableListOf() - var index = 0 - while (index <= this.lastIndex) { - if (this.isNotLeaf(index)) { - children += this[index] - } else { - val startOffset = this[index].startOffset - val sIndex = index - while (index < this.lastIndex) { - if (this.isNotLeaf(index + 1) || this[index + 1].startOffset != this[index].endOffset) { - children += mergedLeafNode(this, index, startOffset, sIndex) - break - } - index++ - } - if (index == this.lastIndex) { - children += mergedLeafNode(this, index, startOffset, sIndex) - } - } - index++ - } - return children - } - - private fun mergedLeafNode(nodes: List<ASTNode>, index: Int, startOffset: Int, sIndex: Int): LeafASTNode { - val endOffset = nodes[index].endOffset - val type = if (nodes.subList(sIndex, index) - .any { it.type == MarkdownTokenTypes.CODE_LINE } - ) MarkdownTokenTypes.CODE_LINE else MarkdownTokenTypes.TEXT - return LeafASTNode(type, startOffset, endOffset) - } - - private fun String.transform() = this - .replace(Regex("\n\n+"), "") // Squashing new lines between paragraphs - .replace(Regex("\n"), " ") - .replace(Regex(" >+ +"), " ") // Replacement used in blockquotes, get rid of garbage - - private fun detailedException(baseMessage: String, node: ASTNode) = - IllegalStateException( - baseMessage + " in ${kdocLocation ?: "unspecified location"}, element starts from offset ${node.startOffset} and ends ${node.endOffset}: ${ - text.substring( - node.startOffset, - node.endOffset - ) - }" - ) - - - companion object { - fun parseFromKDocTag( - kDocTag: KDocTag?, - externalDri: (String) -> DRI?, - kdocLocation: String?, - parseWithChildren: Boolean = true - ): DocumentationNode { - return if (kDocTag == null) { - DocumentationNode(emptyList()) - } else { - fun parseStringToDocNode(text: String) = - MarkdownParser(externalDri, kdocLocation).parseStringToDocNode(text) - - fun pointedLink(tag: KDocTag): DRI? = (parseStringToDocNode("[${tag.getSubjectName()}]")).let { - val link = it.children[0].children[0] - if (link is DocumentationLink) link.dri else null - } - - val allTags = - listOf(kDocTag) + if (kDocTag.canHaveParent() && parseWithChildren) getAllKDocTags(findParent(kDocTag)) else emptyList() - DocumentationNode( - allTags.map { - when (it.knownTag) { - null -> if (it.name == null) Description(parseStringToDocNode(it.getContent())) else CustomTagWrapper( - parseStringToDocNode(it.getContent()), - it.name!! - ) - KDocKnownTag.AUTHOR -> Author(parseStringToDocNode(it.getContent())) - KDocKnownTag.THROWS -> { - val dri = pointedLink(it) - Throws( - parseStringToDocNode(it.getContent()), - dri?.fqDeclarationName() ?: it.getSubjectName().orEmpty(), - dri, - ) - } - KDocKnownTag.EXCEPTION -> { - val dri = pointedLink(it) - Throws( - parseStringToDocNode(it.getContent()), - dri?.fqDeclarationName() ?: it.getSubjectName().orEmpty(), - dri - ) - } - KDocKnownTag.PARAM -> Param( - parseStringToDocNode(it.getContent()), - it.getSubjectName().orEmpty() - ) - KDocKnownTag.RECEIVER -> Receiver(parseStringToDocNode(it.getContent())) - KDocKnownTag.RETURN -> Return(parseStringToDocNode(it.getContent())) - KDocKnownTag.SEE -> { - val dri = pointedLink(it) - See( - parseStringToDocNode(it.getContent()), - dri?.fqDeclarationName() ?: it.getSubjectName().orEmpty(), - dri, - ) - } - KDocKnownTag.SINCE -> Since(parseStringToDocNode(it.getContent())) - KDocKnownTag.CONSTRUCTOR -> Constructor(parseStringToDocNode(it.getContent())) - KDocKnownTag.PROPERTY -> Property( - parseStringToDocNode(it.getContent()), - it.getSubjectName().orEmpty() - ) - KDocKnownTag.SAMPLE -> Sample( - parseStringToDocNode(it.getContent()), - it.getSubjectName().orEmpty() - ) - KDocKnownTag.SUPPRESS -> Suppress(parseStringToDocNode(it.getContent())) - } - } - ) - } - } - - //Horrible hack but since link resolution is passed as a function i am not able to resolve them otherwise - @kotlin.Suppress("DeprecatedCallableAddReplaceWith") - @Deprecated("This function makes wrong assumptions and is missing a lot of corner cases related to generics, " + - "parameters and static members. This is not supposed to be public API and will not be supported in the future") - fun DRI.fqName(): String? = "$packageName.$classNames".takeIf { packageName != null && classNames != null } - - private fun DRI.fqDeclarationName(): String? { - if (this.target !is PointingToDeclaration) { - return null - } - return listOfNotNull(this.packageName, this.classNames, this.callable?.name) - .joinToString(separator = ".") - .takeIf { it.isNotBlank() } - } - - private fun findParent(kDoc: PsiElement): PsiElement = - if (kDoc.canHaveParent()) findParent(kDoc.parent) else kDoc - - private fun PsiElement.canHaveParent(): Boolean = this is KDocSection && knownTag != KDocKnownTag.PROPERTY - - private fun getAllKDocTags(kDocImpl: PsiElement): List<KDocTag> = - kDocImpl.children.filterIsInstance<KDocTag>().filterNot { it is KDocSection } + kDocImpl.children.flatMap { - getAllKDocTags(it) - } - } -} - diff --git a/plugins/base/src/main/kotlin/parsers/Parser.kt b/plugins/base/src/main/kotlin/parsers/Parser.kt deleted file mode 100644 index af07ec53..00000000 --- a/plugins/base/src/main/kotlin/parsers/Parser.kt +++ /dev/null @@ -1,131 +0,0 @@ -package org.jetbrains.dokka.base.parsers - -import org.jetbrains.dokka.model.doc.* -import org.jetbrains.dokka.model.doc.Deprecated -import org.jetbrains.dokka.model.doc.Suppress - -abstract class Parser { - - abstract fun parseStringToDocNode(extractedString: String): DocTag - - abstract fun preparse(text: String): String - - open fun parse(text: String): DocumentationNode = - DocumentationNode(extractTagsToListOfPairs(preparse(text)).map { (tag, content) -> parseTagWithBody(tag, content) }) - - open fun parseTagWithBody(tagName: String, content: String): TagWrapper = - when (tagName) { - "description" -> Description(parseStringToDocNode(content)) - "author" -> Author(parseStringToDocNode(content)) - "version" -> Version(parseStringToDocNode(content)) - "since" -> Since(parseStringToDocNode(content)) - "see" -> See( - parseStringToDocNode(content.substringAfter(' ')), - content.substringBefore(' '), - null - ) - "param" -> Param( - parseStringToDocNode(content.substringAfter(' ')), - content.substringBefore(' ') - ) - "property" -> Property( - parseStringToDocNode(content.substringAfter(' ')), - content.substringBefore(' ') - ) - "return" -> Return(parseStringToDocNode(content)) - "constructor" -> Constructor(parseStringToDocNode(content)) - "receiver" -> Receiver(parseStringToDocNode(content)) - "throws", "exception" -> Throws( - parseStringToDocNode(content.substringAfter(' ')), - content.substringBefore(' '), - null - ) - "deprecated" -> Deprecated(parseStringToDocNode(content)) - "sample" -> Sample( - parseStringToDocNode(content.substringAfter(' ')), - content.substringBefore(' ') - ) - "suppress" -> Suppress(parseStringToDocNode(content)) - else -> CustomTagWrapper(parseStringToDocNode(content), tagName) - } - - /** - * KDoc parser from Kotlin compiler relies on a comment asterisk - * So there is a mini parser here - * TODO: at least to adapt [org.jetbrains.kotlin.kdoc.lexer.KDocLexer] to analyze KDoc without the asterisks and use it here - */ - private fun extractTagsToListOfPairs(text: String): List<Pair<String, String>> = - "description $text" - .extractKDocSections() - .map { content -> - val contentWithEscapedAts = content.replace("\\@", "@") - val (tag, body) = contentWithEscapedAts.split(" ", limit = 2) - tag to body - } - - /** - * Ignore a doc tag inside code spans and blocks - * @see org.jetbrains.kotlin.kdoc.psi.impl.KDocSection - */ - private fun CharSequence.extractKDocSections(delimiter: String = "\n@"): List<String> { - var countOfBackticks = 0 - var countOfTildes = 0 - var countOfBackticksInOpeningFence = 0 - var countOfTildesInOpeningFence = 0 - - var isInCode = false - val result = mutableListOf<String>() - var rangeStart = 0 - var rangeEnd = 0 - var currentOffset = 0 - while (currentOffset < length) { - - when (get(currentOffset)) { - '`' -> { - countOfBackticks++ - countOfTildes = 0 - } - '~' -> { - countOfTildes++ - countOfBackticks = 0 - } - else -> { - if (isInCode) { - // The closing code fence must be at least as long as the opening fence - if(countOfBackticks >= countOfBackticksInOpeningFence - || countOfTildes >= countOfTildesInOpeningFence) - isInCode = false - } else { - // as per CommonMark spec, there can be any number of backticks for a code span, not only one or three - if (countOfBackticks > 0) { - isInCode = true - countOfBackticksInOpeningFence = countOfBackticks - countOfTildesInOpeningFence = Int.MAX_VALUE - } - // tildes are only for a code block, not code span - if (countOfTildes >= 3) { - isInCode = true - countOfTildesInOpeningFence = countOfTildes - countOfBackticksInOpeningFence = Int.MAX_VALUE - } - } - countOfTildes = 0 - countOfBackticks = 0 - } - } - if (!isInCode && startsWith(delimiter, currentOffset)) { - result.add(substring(rangeStart, rangeEnd)) - currentOffset += delimiter.length - rangeStart = currentOffset - rangeEnd = currentOffset - continue - } - - ++rangeEnd - ++currentOffset - } - result.add(substring(rangeStart, rangeEnd)) - return result - } - -} diff --git a/plugins/base/src/main/kotlin/parsers/factories/DocTagsFromIElementFactory.kt b/plugins/base/src/main/kotlin/parsers/factories/DocTagsFromIElementFactory.kt deleted file mode 100644 index fed3f7eb..00000000 --- a/plugins/base/src/main/kotlin/parsers/factories/DocTagsFromIElementFactory.kt +++ /dev/null @@ -1,67 +0,0 @@ -package org.jetbrains.dokka.base.parsers.factories - -import org.jetbrains.dokka.model.doc.* -import org.intellij.markdown.IElementType -import org.intellij.markdown.MarkdownElementTypes -import org.intellij.markdown.MarkdownTokenTypes -import org.intellij.markdown.flavours.gfm.GFMElementTypes -import org.intellij.markdown.flavours.gfm.GFMTokenTypes -import org.jetbrains.dokka.base.translators.parseWithNormalisedSpaces -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.doc.DocTag.Companion.contentTypeParam - -object DocTagsFromIElementFactory { - - @Suppress("IMPLICIT_CAST_TO_ANY") - fun getInstance(type: IElementType, children: List<DocTag> = emptyList(), params: Map<String, String> = emptyMap(), body: String? = null, dri: DRI? = null, keepFormatting: Boolean = false) = - when(type) { - MarkdownElementTypes.SHORT_REFERENCE_LINK, - MarkdownElementTypes.FULL_REFERENCE_LINK, - MarkdownElementTypes.INLINE_LINK -> if(dri == null) A(children, params) else DocumentationLink(dri, children, params) - MarkdownElementTypes.STRONG -> B(children, params) - MarkdownElementTypes.BLOCK_QUOTE -> BlockQuote(children, params) - MarkdownElementTypes.CODE_SPAN -> CodeInline(children, params) - MarkdownElementTypes.CODE_BLOCK, - MarkdownElementTypes.CODE_FENCE -> CodeBlock(children, params) - MarkdownElementTypes.ATX_1 -> H1(children, params) - MarkdownElementTypes.ATX_2 -> H2(children, params) - MarkdownElementTypes.ATX_3 -> H3(children, params) - MarkdownElementTypes.ATX_4 -> H4(children, params) - MarkdownElementTypes.ATX_5 -> H5(children, params) - MarkdownElementTypes.ATX_6 -> H6(children, params) - MarkdownElementTypes.EMPH -> I(children, params) - MarkdownElementTypes.IMAGE -> Img(children, params) - MarkdownElementTypes.LIST_ITEM -> Li(children, params) - MarkdownElementTypes.ORDERED_LIST -> Ol(children, params) - MarkdownElementTypes.UNORDERED_LIST -> Ul(children, params) - MarkdownElementTypes.PARAGRAPH -> P(children, params) - MarkdownTokenTypes.TEXT -> if (keepFormatting) Text( - body.orEmpty(), - children, - params - ) else { - // corner case: there are only spaces between two Markdown nodes - val containsOnlySpaces = body?.isNotEmpty() == true && body.all { it.isWhitespace() } - if (containsOnlySpaces) Text(" ", children, params) - else body?.parseWithNormalisedSpaces(renderWhiteCharactersAsSpaces = false).orEmpty() - } - MarkdownTokenTypes.HORIZONTAL_RULE -> HorizontalRule - MarkdownTokenTypes.HARD_LINE_BREAK -> Br - GFMElementTypes.STRIKETHROUGH -> Strikethrough(children, params) - GFMElementTypes.TABLE -> Table(children, params) - GFMElementTypes.HEADER -> Th(children, params) - GFMElementTypes.ROW -> Tr(children, params) - GFMTokenTypes.CELL -> Td(children, params) - MarkdownElementTypes.MARKDOWN_FILE -> CustomDocTag(children, params, MarkdownElementTypes.MARKDOWN_FILE.name) - MarkdownElementTypes.HTML_BLOCK, - MarkdownTokenTypes.HTML_TAG, - MarkdownTokenTypes.HTML_BLOCK_CONTENT -> Text(body.orEmpty(), params = params + contentTypeParam("html")) - else -> CustomDocTag(children, params, type.name) - }.let { - @Suppress("UNCHECKED_CAST") - when (it) { - is List<*> -> it as List<DocTag> - else -> listOf(it as DocTag) - } - } -} diff --git a/plugins/base/src/main/kotlin/parsers/factories/DocTagsFromStringFactory.kt b/plugins/base/src/main/kotlin/parsers/factories/DocTagsFromStringFactory.kt deleted file mode 100644 index 1af4e719..00000000 --- a/plugins/base/src/main/kotlin/parsers/factories/DocTagsFromStringFactory.kt +++ /dev/null @@ -1,77 +0,0 @@ -package org.jetbrains.dokka.base.parsers.factories - -import org.jetbrains.dokka.model.doc.* -import org.jetbrains.dokka.links.DRI -import java.lang.NullPointerException - -object DocTagsFromStringFactory { - fun getInstance(name: String, children: List<DocTag> = emptyList(), params: Map<String, String> = emptyMap(), body: String? = null, dri: DRI? = null) = - when(name) { - "a" -> A(children, params) - "big" -> Big(children, params) - "b" -> B(children, params) - "blockquote" -> BlockQuote(children, params) - "br" -> Br - "cite" -> Cite(children, params) - "code" -> if(params.isEmpty()) CodeInline(children, params) else CodeBlock(children, params) - "dd" -> Dd(children, params) - "dfn" -> Dfn(children, params) - "dir" -> Dir(children, params) - "div" -> Div(children, params) - "dl" -> Dl(children, params) - "dt" -> Dt(children, params) - "Em" -> Em(children, params) - "font" -> Font(children, params) - "footer" -> Footer(children, params) - "frame" -> Frame(children, params) - "frameset" -> FrameSet(children, params) - "h1" -> H1(children, params) - "h2" -> H2(children, params) - "h3" -> H3(children, params) - "h4" -> H4(children, params) - "h5" -> H5(children, params) - "h6" -> H6(children, params) - "head" -> Head(children, params) - "header" -> Header(children, params) - "html" -> Html(children, params) - "i" -> I(children, params) - "iframe" -> IFrame(children, params) - "img" -> Img(children, params) - "input" -> Input(children, params) - "li" -> Li(children, params) - "link" -> Link(children, params) - "listing" -> Listing(children, params) - "main" -> Main(children, params) - "menu" -> Menu(children, params) - "meta" -> Meta(children, params) - "nav" -> Nav(children, params) - "noframes" -> NoFrames(children, params) - "noscript" -> NoScript(children, params) - "ol" -> Ol(children, params) - "p" -> P(children, params) - "pre" -> Pre(children, params) - "script" -> Script(children, params) - "section" -> Section(children, params) - "small" -> Small(children, params) - "span" -> Span(children, params) - "strong" -> Strong(children, params) - "sub" -> Sub(children, params) - "sup" -> Sup(children, params) - "table" -> Table(children, params) - "#text" -> Text(body ?: throw NullPointerException("Text body should be at least empty string passed to DocNodes factory!"), children, params) - "tBody" -> TBody(children, params) - "td" -> Td(children, params) - "tFoot" -> TFoot(children, params) - "th" -> Th(children, params) - "tHead" -> THead(children, params) - "title" -> Title(children, params) - "tr" -> Tr(children, params) - "tt" -> Tt(children, params) - "u" -> U(children, params) - "ul" -> Ul(children, params) - "var" -> Var(children, params) - "documentationlink" -> DocumentationLink(dri ?: throw NullPointerException("DRI cannot be passed null while constructing documentation link!"), children, params) - "hr" -> HorizontalRule - else -> CustomDocTag(children, params, name) - } -} diff --git a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/IllegalModuleAndPackageDocumentation.kt b/plugins/base/src/main/kotlin/parsers/moduleAndPackage/IllegalModuleAndPackageDocumentation.kt deleted file mode 100644 index f642c374..00000000 --- a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/IllegalModuleAndPackageDocumentation.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.jetbrains.dokka.base.parsers.moduleAndPackage - -import org.jetbrains.dokka.DokkaException - -internal class IllegalModuleAndPackageDocumentation( - source: ModuleAndPackageDocumentationSource, message: String -) : DokkaException("[$source] $message") diff --git a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentation.kt b/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentation.kt deleted file mode 100644 index ee67fad1..00000000 --- a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentation.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.jetbrains.dokka.base.parsers.moduleAndPackage - -import org.jetbrains.dokka.model.doc.DocumentationNode - -data class ModuleAndPackageDocumentation( - val name: String, - val classifier: Classifier, - val documentation: DocumentationNode -) { - enum class Classifier { Module, Package } -} diff --git a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationFragment.kt b/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationFragment.kt deleted file mode 100644 index 06fef72c..00000000 --- a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationFragment.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.jetbrains.dokka.base.parsers.moduleAndPackage - -import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.* - -data class ModuleAndPackageDocumentationFragment( - val name: String, - val classifier: Classifier, - val documentation: String, - val source: ModuleAndPackageDocumentationSource -) diff --git a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationParsingContext.kt b/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationParsingContext.kt deleted file mode 100644 index fa6c653e..00000000 --- a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationParsingContext.kt +++ /dev/null @@ -1,64 +0,0 @@ -package org.jetbrains.dokka.base.parsers.moduleAndPackage - -import org.jetbrains.dokka.analysis.DokkaResolutionFacade -import org.jetbrains.dokka.analysis.from -import org.jetbrains.dokka.base.parsers.MarkdownParser -import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.Module -import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.Package -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.doc.DocumentationNode -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name - -fun interface ModuleAndPackageDocumentationParsingContext { - fun markdownParserFor(fragment: ModuleAndPackageDocumentationFragment, location: String): MarkdownParser -} - -internal fun ModuleAndPackageDocumentationParsingContext.parse( - fragment: ModuleAndPackageDocumentationFragment -): DocumentationNode { - return markdownParserFor(fragment, fragment.source.sourceDescription).parse(fragment.documentation) -} - -fun ModuleAndPackageDocumentationParsingContext( - logger: DokkaLogger, - facade: DokkaResolutionFacade? = null -) = ModuleAndPackageDocumentationParsingContext { fragment, sourceLocation -> - val descriptor = when (fragment.classifier) { - Module -> facade?.moduleDescriptor?.getPackage(FqName.topLevel(Name.identifier(""))) - Package -> facade?.moduleDescriptor?.getPackage(FqName(fragment.name)) - } - - val externalDri = { link: String -> - try { - if (facade != null && descriptor != null) { - resolveKDocLink( - facade.resolveSession.bindingContext, - facade, - descriptor, - null, - link.split('.') - ).sorted().firstOrNull()?.let { DRI.from(it) } - } else null - } catch (e1: IllegalArgumentException) { - logger.warn("Couldn't resolve link for $link") - null - } - } - - MarkdownParser(externalDri = externalDri, sourceLocation) -} - -private fun Collection<DeclarationDescriptor>.sorted() = sortedWith( - compareBy( - { it is ClassDescriptor }, - { (it as? FunctionDescriptor)?.name }, - { (it as? FunctionDescriptor)?.valueParameters?.size }, - { (it as? FunctionDescriptor)?.valueParameters?.joinToString { it.type.toString() } } - ) -) diff --git a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationSource.kt b/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationSource.kt deleted file mode 100644 index 9514adb4..00000000 --- a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationSource.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.jetbrains.dokka.base.parsers.moduleAndPackage - -import java.io.File - -abstract class ModuleAndPackageDocumentationSource { - abstract val sourceDescription: String - abstract val documentation: String - override fun toString(): String = sourceDescription -} - -internal data class ModuleAndPackageDocumentationFile(private val file: File) : ModuleAndPackageDocumentationSource() { - override val sourceDescription: String = file.path - override val documentation: String by lazy(LazyThreadSafetyMode.PUBLICATION) { file.readText() } -} diff --git a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/parseModuleAndPackageDocumentation.kt b/plugins/base/src/main/kotlin/parsers/moduleAndPackage/parseModuleAndPackageDocumentation.kt deleted file mode 100644 index db342042..00000000 --- a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/parseModuleAndPackageDocumentation.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.jetbrains.dokka.base.parsers.moduleAndPackage - -fun parseModuleAndPackageDocumentation( - context: ModuleAndPackageDocumentationParsingContext, - fragment: ModuleAndPackageDocumentationFragment -): ModuleAndPackageDocumentation { - return ModuleAndPackageDocumentation( - name = fragment.name, - classifier = fragment.classifier, - documentation = context.parse(fragment) - ) -} diff --git a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/parseModuleAndPackageDocumentationFragments.kt b/plugins/base/src/main/kotlin/parsers/moduleAndPackage/parseModuleAndPackageDocumentationFragments.kt deleted file mode 100644 index d3381901..00000000 --- a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/parseModuleAndPackageDocumentationFragments.kt +++ /dev/null @@ -1,55 +0,0 @@ -package org.jetbrains.dokka.base.parsers.moduleAndPackage - -import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.* -import java.io.File - - -fun parseModuleAndPackageDocumentationFragments(source: File): List<ModuleAndPackageDocumentationFragment> { - return parseModuleAndPackageDocumentationFragments(ModuleAndPackageDocumentationFile(source)) -} - -fun parseModuleAndPackageDocumentationFragments( - source: ModuleAndPackageDocumentationSource -): List<ModuleAndPackageDocumentationFragment> { - val fragmentStrings = source.documentation.split(Regex("(|^)#\\s*(?=(Module|Package))")) - return fragmentStrings - .filter(String::isNotBlank) - .map { fragmentString -> parseModuleAndPackageDocFragment(source, fragmentString) } -} - -private fun parseModuleAndPackageDocFragment( - source: ModuleAndPackageDocumentationSource, - fragment: String -): ModuleAndPackageDocumentationFragment { - val firstLineAndDocumentation = fragment.split("\r\n", "\n", "\r", limit = 2) - val firstLine = firstLineAndDocumentation[0] - - val classifierAndName = firstLine.split(Regex("\\s+"), limit = 2) - - val classifier = when (classifierAndName[0].trim()) { - "Module" -> Module - "Package" -> Package - else -> throw IllegalStateException( - """Unexpected classifier: "${classifierAndName[0]}", expected either "Module" or "Package". - |For more information consult the specification: https://kotlinlang.org/docs/dokka-module-and-package-docs.html""".trimMargin() - ) - } - - if (classifierAndName.size != 2 && classifier == Module) { - throw IllegalModuleAndPackageDocumentation(source, "Missing Module name") - } - - val name = classifierAndName.getOrNull(1)?.trim().orEmpty() - if (classifier == Package && name.contains(Regex("\\s"))) { - throw IllegalModuleAndPackageDocumentation( - source, "Package name cannot contain whitespace in '$firstLine'" - ) - } - - return ModuleAndPackageDocumentationFragment( - name = name, - classifier = classifier, - documentation = firstLineAndDocumentation.getOrNull(1)?.trim().orEmpty(), - source = source - ) -} diff --git a/plugins/base/src/main/kotlin/renderers/PackageListService.kt b/plugins/base/src/main/kotlin/renderers/PackageListService.kt index 2bf66ebf..79391a1c 100644 --- a/plugins/base/src/main/kotlin/renderers/PackageListService.kt +++ b/plugins/base/src/main/kotlin/renderers/PackageListService.kt @@ -3,14 +3,13 @@ package org.jetbrains.dokka.base.renderers import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.resolvers.shared.LinkFormat import org.jetbrains.dokka.base.resolvers.shared.PackageList.Companion.DOKKA_PARAM_PREFIX -import org.jetbrains.dokka.base.resolvers.shared.PackageList.Companion.SINGLE_MODULE_NAME import org.jetbrains.dokka.base.resolvers.shared.PackageList.Companion.MODULE_DELIMITER +import org.jetbrains.dokka.base.resolvers.shared.PackageList.Companion.SINGLE_MODULE_NAME import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.plugin import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.kotlin.utils.addToStdlib.safeAs class PackageListService(val context: DokkaContext, val rootPage: RootPageNode) { @@ -29,7 +28,7 @@ class PackageListService(val context: DokkaContext, val rootPage: RootPageNode) ?.let { packages.add(it) } } - val contentPage = node.safeAs<ContentPage>() + val contentPage = node as? ContentPage contentPage?.dri?.forEach { dri -> val nodeLocation = locationProvider.resolve(node, context = module, skipExtension = true) ?: run { context.logger.error("Cannot resolve path for ${node.name}!"); null } diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index 94bd0aeb..fa343eec 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -23,7 +23,6 @@ import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.pages.HtmlContent import org.jetbrains.dokka.plugability.* import org.jetbrains.dokka.utilities.htmlEscape -import org.jetbrains.kotlin.utils.addIfNotNull internal const val TEMPLATE_REPLACEMENT: String = "###" internal const val TOGGLEABLE_CONTENT_TYPE_ATTR = "data-togglable" @@ -355,12 +354,10 @@ open class HtmlRenderer( val contentOfSourceSet = mutableListOf<ContentNode>() distinct.onEachIndexed{ index, (_, distinctInstances) -> - contentOfSourceSet.addIfNotNull(distinctInstances.firstOrNull()?.before) + distinctInstances.firstOrNull()?.before?.let { contentOfSourceSet.add(it) } contentOfSourceSet.addAll(distinctInstances.map { it.divergent }) - contentOfSourceSet.addIfNotNull( - distinctInstances.firstOrNull()?.after - ?: if (index != distinct.size - 1) ContentBreakLine(it.key) else null - ) + (distinctInstances.firstOrNull()?.after ?: if (index != distinct.size - 1) ContentBreakLine(it.key) else null) + ?.let { contentOfSourceSet.add(it) } // content kind main is important for declarations list to avoid double line breaks if (node.dci.kind == ContentKind.Main && index != distinct.size - 1) { diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt index be1b0fcf..c864295c 100644 --- a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt +++ b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt @@ -4,13 +4,20 @@ import org.jetbrains.dokka.base.renderers.sourceSets import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.annotations import org.jetbrains.dokka.base.transformers.documentables.isDeprecated import org.jetbrains.dokka.base.transformers.documentables.isException -import org.jetbrains.dokka.base.translators.documentables.DocumentableLanguage -import org.jetbrains.dokka.base.translators.documentables.documentableLanguage import org.jetbrains.dokka.base.utils.canonicalAlphabeticalOrder import org.jetbrains.dokka.model.* import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.kotlin.analysis.kotlin.internal.DocumentableLanguage +import org.jetbrains.kotlin.analysis.kotlin.internal.InternalKotlinAnalysisPlugin + +abstract class NavigationDataProvider( + dokkaContext: DokkaContext +) { + private val documentableSourceLanguageParser = dokkaContext.plugin<InternalKotlinAnalysisPlugin>().querySingle { documentableSourceLanguageParser } -abstract class NavigationDataProvider { open fun navigableChildren(input: RootPageNode): NavigationNode = input.withDescendants() .first { it is ModulePage || it is MultimoduleRootPage }.let { visit(it as ContentPage) } @@ -68,8 +75,9 @@ abstract class NavigationDataProvider { } private fun Documentable.hasAnyJavaSources(): Boolean { - val withSources = this as? WithSources ?: return false - return this.sourceSets.any { withSources.documentableLanguage(it) == DocumentableLanguage.JAVA } + return this.sourceSets.any { sourceSet -> + documentableSourceLanguageParser.getLanguage(this, sourceSet) == DocumentableLanguage.JAVA + } } private fun DClass.isAbstract() = diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt index 9543c388..4def7088 100644 --- a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt +++ b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt @@ -2,6 +2,8 @@ package org.jetbrains.dokka.base.renderers.html import kotlinx.html.* import kotlinx.html.stream.createHTML +import org.jetbrains.dokka.base.renderers.html.NavigationNodeIcon.CLASS +import org.jetbrains.dokka.base.renderers.html.NavigationNodeIcon.CLASS_KT import org.jetbrains.dokka.base.renderers.pageId import org.jetbrains.dokka.base.templating.AddToNavigationCommand import org.jetbrains.dokka.links.DRI diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt index a213bce9..557205d7 100644 --- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt +++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt @@ -11,7 +11,7 @@ import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.configuration import org.jetbrains.dokka.transformers.pages.PageTransformer -open class NavigationPageInstaller(val context: DokkaContext) : NavigationDataProvider(), PageTransformer { +open class NavigationPageInstaller(val context: DokkaContext) : NavigationDataProvider(context), PageTransformer { override fun invoke(input: RootPageNode): RootPageNode = input.modified( children = input.children + NavigationPage( diff --git a/plugins/base/src/main/kotlin/renderers/preprocessors.kt b/plugins/base/src/main/kotlin/renderers/preprocessors.kt index 8a30bed1..2cfa2dcf 100644 --- a/plugins/base/src/main/kotlin/renderers/preprocessors.kt +++ b/plugins/base/src/main/kotlin/renderers/preprocessors.kt @@ -1,12 +1,7 @@ package org.jetbrains.dokka.base.renderers import org.jetbrains.dokka.base.resolvers.shared.LinkFormat -import org.jetbrains.dokka.pages.ModulePage -import org.jetbrains.dokka.pages.RendererSpecificPage -import org.jetbrains.dokka.pages.RendererSpecificResourcePage -import org.jetbrains.dokka.pages.RendererSpecificRootPage -import org.jetbrains.dokka.pages.RenderingStrategy -import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.transformers.pages.PageTransformer diff --git a/plugins/base/src/main/kotlin/resolvers/external/javadoc/JavadocExternalLocationProvider.kt b/plugins/base/src/main/kotlin/resolvers/external/javadoc/JavadocExternalLocationProvider.kt index a1f1542d..3ac987f6 100644 --- a/plugins/base/src/main/kotlin/resolvers/external/javadoc/JavadocExternalLocationProvider.kt +++ b/plugins/base/src/main/kotlin/resolvers/external/javadoc/JavadocExternalLocationProvider.kt @@ -2,7 +2,10 @@ package org.jetbrains.dokka.base.resolvers.external.javadoc import org.jetbrains.dokka.base.resolvers.external.DefaultExternalLocationProvider import org.jetbrains.dokka.base.resolvers.shared.ExternalDocumentation -import org.jetbrains.dokka.links.* +import org.jetbrains.dokka.links.Callable +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.DRIExtraContainer +import org.jetbrains.dokka.links.EnumEntryDRIExtra import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.utilities.htmlEscape diff --git a/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt b/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt index f6c4f0db..e589da15 100644 --- a/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt +++ b/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt @@ -1,13 +1,13 @@ package org.jetbrains.dokka.base.signatures +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.drisOfAllNestedBounds import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.AnnotationTarget import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.drisOfAllNestedBounds -import org.jetbrains.dokka.model.AnnotationTarget interface JvmSignatureUtils { diff --git a/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt b/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt index dfb0d3f7..8f278545 100644 --- a/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt +++ b/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt @@ -2,7 +2,6 @@ package org.jetbrains.dokka.base.signatures import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet import org.jetbrains.dokka.Platform -import org.jetbrains.dokka.analysis.DescriptorDocumentableSource import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.dri import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.driOrNull @@ -18,15 +17,14 @@ import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.plugin import org.jetbrains.dokka.plugability.querySingle import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi -import org.jetbrains.kotlin.psi.KtParameter import kotlin.text.Typography.nbsp class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLogger) : SignatureProvider, JvmSignatureUtils by KotlinSignatureUtils { + constructor(context: DokkaContext) : this( context.plugin<DokkaBase>().querySingle { commentsToContentConverter }, - context.logger + context.logger, ) private val contentBuilder = PageContentBuilder(ctcc, this, logger) @@ -222,8 +220,12 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog * An example would be a primary constructor `class A(val s: String)`, * where `s` is both a function parameter and a property */ - private fun DProperty.isAlsoParameter(sourceSet: DokkaSourceSet) = - (this.sources[sourceSet] as? DescriptorDocumentableSource)?.descriptor?.findPsi() is KtParameter + private fun DProperty.isAlsoParameter(sourceSet: DokkaSourceSet): Boolean { + return this.extra[IsAlsoParameter] + ?.inSourceSets + ?.any { it.sourceSetID == sourceSet.sourceSetID } + ?: false + } private fun propertySignature(p: DProperty) = p.sourceSets.map { sourceSet -> diff --git a/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt b/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt index ae5275a5..e47c85b9 100644 --- a/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt +++ b/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt @@ -1,14 +1,14 @@ package org.jetbrains.dokka.base.signatures -import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder import org.jetbrains.dokka.base.transformers.pages.annotations.SinceKotlinTransformer -import org.jetbrains.dokka.pages.ContentKind +import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.DriOfAny import org.jetbrains.dokka.links.DriOfUnit import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.AnnotationTarget import org.jetbrains.dokka.model.properties.WithExtraProperties +import org.jetbrains.dokka.pages.ContentKind object KotlinSignatureUtils : JvmSignatureUtils { diff --git a/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt b/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt index 17e3cbcd..d9c68981 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt @@ -1,13 +1,13 @@ package org.jetbrains.dokka.base.transformers.documentables import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.base.utils.firstNotNullOfOrNull import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.properties.ExtraProperty import org.jetbrains.dokka.model.properties.MergeStrategy import org.jetbrains.dokka.model.properties.mergeExtras import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.transformers.documentation.DocumentableMerger -import org.jetbrains.kotlin.util.firstNotNullResult internal class DefaultDocumentableMerger(val context: DokkaContext) : DocumentableMerger { private val dependencyInfo = context.getDependencyInfo() @@ -21,7 +21,7 @@ internal class DefaultDocumentableMerger(val context: DokkaContext) : Documentab list.flatMap { it.packages } ) { pck1, pck2 -> pck1.mergeWith(pck2) }, documentation = list.map { it.documentation }.flatMap { it.entries }.associate { (k, v) -> k to v }, - expectPresentInSet = list.firstNotNullResult { it.expectPresentInSet }, + expectPresentInSet = list.firstNotNullOfOrNull { it.expectPresentInSet }, sourceSets = list.flatMap { it.sourceSets }.toSet() ).mergeExtras(left, right) } diff --git a/plugins/base/src/main/kotlin/transformers/documentables/DeprecatedDocumentableFilterTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/DeprecatedDocumentableFilterTransformer.kt index 1112ac15..366690c7 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/DeprecatedDocumentableFilterTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/DeprecatedDocumentableFilterTransformer.kt @@ -1,7 +1,7 @@ package org.jetbrains.dokka.base.transformers.documentables -import org.jetbrains.dokka.DokkaConfiguration.PackageOptions import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaConfiguration.PackageOptions import org.jetbrains.dokka.model.Annotations import org.jetbrains.dokka.model.Documentable import org.jetbrains.dokka.model.EnumValue diff --git a/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilterTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilterTransformer.kt index 94688799..713166bb 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilterTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilterTransformer.kt @@ -1,11 +1,11 @@ package org.jetbrains.dokka.base.transformers.documentables import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.DokkaDefaults import org.jetbrains.dokka.model.* import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer -import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.DokkaDefaults class DocumentableVisibilityFilterTransformer(val context: DokkaContext) : PreMergeDocumentableTransformer { diff --git a/plugins/base/src/main/kotlin/transformers/documentables/ExtensionExtractorTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/ExtensionExtractorTransformer.kt index 19af0564..79df844e 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/ExtensionExtractorTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/ExtensionExtractorTransformer.kt @@ -2,7 +2,6 @@ package org.jetbrains.dokka.base.transformers.documentables import kotlinx.coroutines.* import kotlinx.coroutines.channels.* -import org.jetbrains.dokka.base.transformers.documentables.utils.FullClassHierarchyBuilder import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.DriOfAny import org.jetbrains.dokka.model.* @@ -10,16 +9,19 @@ import org.jetbrains.dokka.model.properties.ExtraProperty import org.jetbrains.dokka.model.properties.MergeStrategy import org.jetbrains.dokka.model.properties.plus import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer import org.jetbrains.dokka.utilities.parallelForEach import org.jetbrains.dokka.utilities.parallelMap +import org.jetbrains.kotlin.analysis.kotlin.internal.InternalKotlinAnalysisPlugin class ExtensionExtractorTransformer : DocumentableTransformer { override fun invoke(original: DModule, context: DokkaContext): DModule = runBlocking(Dispatchers.Default) { val classGraph = async { if (!context.configuration.suppressInheritedMembers) - FullClassHierarchyBuilder()(original) + context.plugin<InternalKotlinAnalysisPlugin>().querySingle { fullClassHierarchyBuilder }.build(original) else emptyMap() } diff --git a/plugins/base/src/main/kotlin/transformers/documentables/InheritedEntriesDocumentableFilterTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/InheritedEntriesDocumentableFilterTransformer.kt index 2e4b29ff..01438432 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/InheritedEntriesDocumentableFilterTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/InheritedEntriesDocumentableFilterTransformer.kt @@ -1,6 +1,7 @@ package org.jetbrains.dokka.base.transformers.documentables -import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.InheritedMember import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.plugability.DokkaContext @@ -14,4 +15,4 @@ class InheritedEntriesDocumentableFilterTransformer(context: DokkaContext) : return context.configuration.suppressInheritedMembers && containsInheritedFrom } -}
\ No newline at end of file +} diff --git a/plugins/base/src/main/kotlin/transformers/documentables/InheritorsExtractorTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/InheritorsExtractorTransformer.kt index 463ec419..6106466f 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/InheritorsExtractorTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/InheritorsExtractorTransformer.kt @@ -1,11 +1,11 @@ package org.jetbrains.dokka.base.transformers.documentables +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.properties.ExtraProperty import org.jetbrains.dokka.model.properties.MergeStrategy import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer class InheritorsExtractorTransformer : DocumentableTransformer { diff --git a/plugins/base/src/main/kotlin/transformers/documentables/ModuleAndPackageDocumentationReader.kt b/plugins/base/src/main/kotlin/transformers/documentables/ModuleAndPackageDocumentationReader.kt deleted file mode 100644 index faf94db2..00000000 --- a/plugins/base/src/main/kotlin/transformers/documentables/ModuleAndPackageDocumentationReader.kt +++ /dev/null @@ -1,107 +0,0 @@ -package org.jetbrains.dokka.base.transformers.documentables - -import org.jetbrains.dokka.DokkaConfiguration -import org.jetbrains.dokka.analysis.KotlinAnalysis -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier -import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentationFragment -import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentationParsingContext -import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentation -import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentationFragments -import org.jetbrains.dokka.model.DModule -import org.jetbrains.dokka.model.DPackage -import org.jetbrains.dokka.model.SourceSetDependent -import org.jetbrains.dokka.model.doc.* -import org.jetbrains.dokka.model.doc.Deprecated -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.dokka.utilities.associateWithNotNull - -internal interface ModuleAndPackageDocumentationReader { - operator fun get(module: DModule): SourceSetDependent<DocumentationNode> - operator fun get(pkg: DPackage): SourceSetDependent<DocumentationNode> -} - -internal fun ModuleAndPackageDocumentationReader(context: DokkaContext): ModuleAndPackageDocumentationReader = - ContextModuleAndPackageDocumentationReader(context) - -private class ContextModuleAndPackageDocumentationReader( - private val context: DokkaContext -) : ModuleAndPackageDocumentationReader { - - private val kotlinAnalysis: KotlinAnalysis = context.plugin<DokkaBase>().querySingle { kotlinAnalysis } - - private val documentationFragments: SourceSetDependent<List<ModuleAndPackageDocumentationFragment>> = - context.configuration.sourceSets.associateWith { sourceSet -> - sourceSet.includes.flatMap { include -> parseModuleAndPackageDocumentationFragments(include) } - } - - private fun findDocumentationNodes( - sourceSets: Set<DokkaConfiguration.DokkaSourceSet>, - predicate: (ModuleAndPackageDocumentationFragment) -> Boolean - ): SourceSetDependent<DocumentationNode> { - return sourceSets.associateWithNotNull { sourceSet -> - val fragments = documentationFragments[sourceSet].orEmpty().filter(predicate) - val resolutionFacade = kotlinAnalysis[sourceSet].facade - val documentations = fragments.map { fragment -> - parseModuleAndPackageDocumentation( - context = ModuleAndPackageDocumentationParsingContext(context.logger, resolutionFacade), - fragment = fragment - ) - } - when (documentations.size) { - 0 -> null - 1 -> documentations.single().documentation - else -> DocumentationNode(documentations.flatMap { it.documentation.children } - .mergeDocumentationNodes()) - } - } - } - - private val ModuleAndPackageDocumentationFragment.canonicalPackageName: String - get() { - check(classifier == Classifier.Package) - if (name == "[root]") return "" - return name - } - - override fun get(module: DModule): SourceSetDependent<DocumentationNode> { - return findDocumentationNodes(module.sourceSets) { fragment -> - fragment.classifier == Classifier.Module && (fragment.name == module.name) - } - } - - override fun get(pkg: DPackage): SourceSetDependent<DocumentationNode> { - return findDocumentationNodes(pkg.sourceSets) { fragment -> - fragment.classifier == Classifier.Package && fragment.canonicalPackageName == pkg.dri.packageName - } - } - - private fun List<TagWrapper>.mergeDocumentationNodes(): List<TagWrapper> = - groupBy { it::class }.values.map { - it.reduce { acc, tagWrapper -> - val newRoot = CustomDocTag( - acc.children + tagWrapper.children, - name = (tagWrapper as? NamedTagWrapper)?.name.orEmpty() - ) - when (acc) { - is See -> acc.copy(newRoot) - is Param -> acc.copy(newRoot) - is Throws -> acc.copy(newRoot) - is Sample -> acc.copy(newRoot) - is Property -> acc.copy(newRoot) - is CustomTagWrapper -> acc.copy(newRoot) - is Description -> acc.copy(newRoot) - is Author -> acc.copy(newRoot) - is Version -> acc.copy(newRoot) - is Since -> acc.copy(newRoot) - is Return -> acc.copy(newRoot) - is Receiver -> acc.copy(newRoot) - is Constructor -> acc.copy(newRoot) - is Deprecated -> acc.copy(newRoot) - is org.jetbrains.dokka.model.doc.Suppress -> acc.copy(newRoot) - } - } - } -} diff --git a/plugins/base/src/main/kotlin/transformers/documentables/ModuleAndPackageDocumentationTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/ModuleAndPackageDocumentationTransformer.kt index 99fba9f7..9f10873b 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/ModuleAndPackageDocumentationTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/ModuleAndPackageDocumentationTransformer.kt @@ -5,21 +5,27 @@ import org.jetbrains.dokka.model.DModule import org.jetbrains.dokka.model.SourceSetDependent import org.jetbrains.dokka.model.doc.DocumentationNode import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer +import org.jetbrains.kotlin.analysis.kotlin.internal.InternalKotlinAnalysisPlugin +import org.jetbrains.kotlin.analysis.kotlin.internal.ModuleAndPackageDocumentationReader internal class ModuleAndPackageDocumentationTransformer( private val moduleAndPackageDocumentationReader: ModuleAndPackageDocumentationReader ) : PreMergeDocumentableTransformer { - constructor(context: DokkaContext) : this(ModuleAndPackageDocumentationReader(context)) + constructor(context: DokkaContext) : this( + context.plugin<InternalKotlinAnalysisPlugin>().querySingle { moduleAndPackageDocumentationReader } + ) override fun invoke(modules: List<DModule>): List<DModule> { return modules.map { module -> module.copy( - documentation = module.documentation + moduleAndPackageDocumentationReader[module], + documentation = module.documentation + moduleAndPackageDocumentationReader.read(module), packages = module.packages.map { pkg -> pkg.copy( - documentation = pkg.documentation + moduleAndPackageDocumentationReader[pkg] + documentation = pkg.documentation + moduleAndPackageDocumentationReader.read(pkg) ) } ) diff --git a/plugins/base/src/main/kotlin/transformers/documentables/ReportUndocumentedTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/ReportUndocumentedTransformer.kt index ad9e34df..3368ded1 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/ReportUndocumentedTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/ReportUndocumentedTransformer.kt @@ -2,14 +2,12 @@ package org.jetbrains.dokka.base.transformers.documentables import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.analysis.DescriptorDocumentableSource import org.jetbrains.dokka.model.* import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer -import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor -import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE -import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED -import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.kotlin.analysis.kotlin.internal.InternalKotlinAnalysisPlugin internal class ReportUndocumentedTransformer : DocumentableTransformer { @@ -19,14 +17,14 @@ internal class ReportUndocumentedTransformer : DocumentableTransformer { private fun invoke(documentable: Documentable, context: DokkaContext) { documentable.sourceSets.forEach { sourceSet -> - if (shouldBeReportedIfNotDocumented(documentable, sourceSet)) { + if (shouldBeReportedIfNotDocumented(documentable, sourceSet, context)) { reportIfUndocumented(context, documentable, sourceSet) } } } private fun shouldBeReportedIfNotDocumented( - documentable: Documentable, sourceSet: DokkaSourceSet + documentable: Documentable, sourceSet: DokkaSourceSet, context: DokkaContext ): Boolean { val packageOptionsOrNull = packageOptionsOrNull(sourceSet, documentable) @@ -42,11 +40,8 @@ internal class ReportUndocumentedTransformer : DocumentableTransformer { return false } - if (isFakeOverride(documentable, sourceSet)) { - return false - } - - if (isSynthesized(documentable, sourceSet)) { + val syntheticDetector = context.plugin<InternalKotlinAnalysisPlugin>().querySingle { syntheticDocumentableDetector } + if (syntheticDetector.isSynthetic(documentable, sourceSet)) { return false } @@ -118,28 +113,8 @@ internal class ReportUndocumentedTransformer : DocumentableTransformer { return documentable.isConstructor } - private fun isFakeOverride(documentable: Documentable, sourceSet: DokkaSourceSet): Boolean { - return callableMemberDescriptorOrNull(documentable, sourceSet)?.kind == FAKE_OVERRIDE - } - - private fun isSynthesized(documentable: Documentable, sourceSet: DokkaSourceSet): Boolean { - return callableMemberDescriptorOrNull(documentable, sourceSet)?.kind == SYNTHESIZED - } - - private fun callableMemberDescriptorOrNull( - documentable: Documentable, sourceSet: DokkaSourceSet - ): CallableMemberDescriptor? { - if (documentable is WithSources) { - return documentable.sources[sourceSet] - .safeAs<DescriptorDocumentableSource>()?.descriptor - .safeAs<CallableMemberDescriptor>() - } - - return null - } - private fun isPrivateOrInternalApi(documentable: Documentable, sourceSet: DokkaSourceSet): Boolean { - return when (documentable.safeAs<WithVisibility>()?.visibility?.get(sourceSet)) { + return when ((documentable as? WithVisibility)?.visibility?.get(sourceSet)) { KotlinVisibility.Public -> false KotlinVisibility.Private -> true KotlinVisibility.Protected -> true diff --git a/plugins/base/src/main/kotlin/transformers/documentables/SuppressTagDocumentableFilter.kt b/plugins/base/src/main/kotlin/transformers/documentables/SuppressTagDocumentableFilter.kt index a297908d..2d65e98b 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/SuppressTagDocumentableFilter.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/SuppressTagDocumentableFilter.kt @@ -2,11 +2,11 @@ package org.jetbrains.dokka.base.transformers.documentables import org.jetbrains.dokka.model.Documentable import org.jetbrains.dokka.model.dfs -import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.model.doc.Suppress +import org.jetbrains.dokka.plugability.DokkaContext class SuppressTagDocumentableFilter(val dokkaContext: DokkaContext) : SuppressedByConditionDocumentableFilterTransformer(dokkaContext) { override fun shouldBeSuppressed(d: Documentable): Boolean = d.documentation.any { (_, docs) -> docs.dfs { it is Suppress } != null } -}
\ No newline at end of file +} diff --git a/plugins/base/src/main/kotlin/transformers/documentables/utils/FullClassHierarchyBuilder.kt b/plugins/base/src/main/kotlin/transformers/documentables/utils/FullClassHierarchyBuilder.kt deleted file mode 100644 index d657fa32..00000000 --- a/plugins/base/src/main/kotlin/transformers/documentables/utils/FullClassHierarchyBuilder.kt +++ /dev/null @@ -1,84 +0,0 @@ -package org.jetbrains.dokka.base.transformers.documentables.utils - -import com.intellij.psi.PsiClass -import kotlinx.coroutines.* -import org.jetbrains.dokka.analysis.DescriptorDocumentableSource -import org.jetbrains.dokka.analysis.PsiDocumentableSource -import org.jetbrains.dokka.analysis.from -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.* -import org.jetbrains.dokka.utilities.parallelForEach -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes -import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny -import java.util.concurrent.ConcurrentHashMap - -typealias Supertypes = List<DRI> -typealias ClassHierarchy = SourceSetDependent<Map<DRI, Supertypes>> - -class FullClassHierarchyBuilder { - suspend operator fun invoke(original: DModule): ClassHierarchy = coroutineScope { - val map = original.sourceSets.associateWith { ConcurrentHashMap<DRI, List<DRI>>() } - original.packages.parallelForEach { visitDocumentable(it, map) } - map - } - - private suspend fun collectSupertypesFromKotlinType( - driWithKType: Pair<DRI, KotlinType>, - supersMap: MutableMap<DRI, Supertypes> - ): Unit = coroutineScope { - val (dri, kotlinType) = driWithKType - val supertypes = kotlinType.immediateSupertypes().filterNot { it.isAnyOrNullableAny() } - val supertypesDriWithKType = supertypes.mapNotNull { supertype -> - supertype.constructor.declarationDescriptor?.let { - DRI.from(it) to supertype - } - } - - if (supersMap[dri] == null) { - // another thread can rewrite the same value, but it isn't a problem - supersMap[dri] = supertypesDriWithKType.map { it.first } - supertypesDriWithKType.parallelForEach { collectSupertypesFromKotlinType(it, supersMap) } - } - } - - private suspend fun collectSupertypesFromPsiClass( - driWithPsiClass: Pair<DRI, PsiClass>, - supersMap: MutableMap<DRI, Supertypes> - ): Unit = coroutineScope { - val (dri, psiClass) = driWithPsiClass - val supertypes = psiClass.superTypes.mapNotNull { it.resolve() } - .filterNot { it.qualifiedName == "java.lang.Object" } - val supertypesDriWithPsiClass = supertypes.map { DRI.from(it) to it } - - if (supersMap[dri] == null) { - // another thread can rewrite the same value, but it isn't a problem - supersMap[dri] = supertypesDriWithPsiClass.map { it.first } - supertypesDriWithPsiClass.parallelForEach { collectSupertypesFromPsiClass(it, supersMap) } - } - } - - private suspend fun visitDocumentable( - documentable: Documentable, - hierarchy: SourceSetDependent<MutableMap<DRI, List<DRI>>> - ): Unit = coroutineScope { - if (documentable is WithScope) { - documentable.classlikes.parallelForEach { visitDocumentable(it, hierarchy) } - } - if (documentable is DClasslike) { - // to build a full class graph, using supertypes from Documentable - // is not enough since it keeps only one level of hierarchy - documentable.sources.forEach { (sourceSet, source) -> - if (source is DescriptorDocumentableSource) { - val descriptor = source.descriptor as ClassDescriptor - val type = descriptor.defaultType - hierarchy[sourceSet]?.let { collectSupertypesFromKotlinType(documentable.dri to type, it) } - } else if (source is PsiDocumentableSource) { - val psi = source.psi as PsiClass - hierarchy[sourceSet]?.let { collectSupertypesFromPsiClass(documentable.dri to psi, it) } - } - } - } - } -}
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/transformers/pages/annotations/SinceKotlinTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/annotations/SinceKotlinTransformer.kt index 75f43324..23b3f625 100644 --- a/plugins/base/src/main/kotlin/transformers/pages/annotations/SinceKotlinTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/pages/annotations/SinceKotlinTransformer.kt @@ -1,9 +1,9 @@ package org.jetbrains.dokka.base.transformers.pages.annotations -import com.intellij.util.containers.ComparatorUtil.max -import org.intellij.markdown.MarkdownElementTypes + import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.analysis.markdown.jb.MARKDOWN_ELEMENT_FILE_NAME import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.annotations import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.doc.CustomDocTag @@ -13,7 +13,6 @@ import org.jetbrains.dokka.model.doc.Text import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer import org.jetbrains.dokka.utilities.associateWithNotNull -import org.jetbrains.kotlin.utils.addToStdlib.safeAs class SinceKotlinVersion constructor(str: String) : Comparable<SinceKotlinVersion> { private val parts: List<Int> = str.split(".").map { it.toInt() } @@ -124,7 +123,7 @@ class SinceKotlinTransformer(val context: DokkaContext) : DocumentableTransforme val annotatedVersion = annotations()[sourceSet] ?.findSinceKotlinAnnotation() - ?.params?.get("version").safeAs<StringValue>()?.value + ?.params?.let { it["version"] as? StringValue }?.value ?.let { SinceKotlinVersion(it) } val minSinceKotlin = minSinceKotlinVersionOfPlatform[sourceSet.analysisPlatform] @@ -139,7 +138,7 @@ class SinceKotlinTransformer(val context: DokkaContext) : DocumentableTransforme val version = getVersion(sourceSet) val parentVersion = parent?.get(sourceSet) if (parentVersion != null) - max(version, parentVersion) + maxOf(version, parentVersion) else version } @@ -157,7 +156,7 @@ class SinceKotlinTransformer(val context: DokkaContext) : DocumentableTransforme version.toString() ) ), - name = MarkdownElementTypes.MARKDOWN_FILE.name + name = MARKDOWN_ELEMENT_FILE_NAME ), "Since Kotlin" ) diff --git a/plugins/base/src/main/kotlin/transformers/pages/comments/CommentsToContentConverter.kt b/plugins/base/src/main/kotlin/transformers/pages/comments/CommentsToContentConverter.kt index fa9ce37e..58e22103 100644 --- a/plugins/base/src/main/kotlin/transformers/pages/comments/CommentsToContentConverter.kt +++ b/plugins/base/src/main/kotlin/transformers/pages/comments/CommentsToContentConverter.kt @@ -3,7 +3,9 @@ package org.jetbrains.dokka.base.transformers.pages.comments import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet import org.jetbrains.dokka.model.doc.DocTag import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.pages.ContentNode +import org.jetbrains.dokka.pages.DCI +import org.jetbrains.dokka.pages.Style interface CommentsToContentConverter { fun buildContent( diff --git a/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt b/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt index 2193283c..083a82cc 100644 --- a/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt +++ b/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt @@ -1,13 +1,14 @@ package org.jetbrains.dokka.base.transformers.pages.comments -import org.intellij.markdown.MarkdownElementTypes + import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.analysis.markdown.jb.MARKDOWN_ELEMENT_FILE_NAME import org.jetbrains.dokka.model.doc.* import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.model.properties.plus import org.jetbrains.dokka.model.toDisplaySourceSets import org.jetbrains.dokka.pages.* -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import org.jetbrains.dokka.utilities.firstIsInstanceOrNull open class DocTagToContentConverter : CommentsToContentConverter { override fun buildContent( @@ -261,5 +262,5 @@ open class DocTagToContentConverter : CommentsToContentConverter { } } - private fun CustomDocTag.isNonemptyFile() = name == MarkdownElementTypes.MARKDOWN_FILE.name && children.size > 1 + private fun CustomDocTag.isNonemptyFile() = name == MARKDOWN_ELEMENT_FILE_NAME && children.size > 1 } diff --git a/plugins/base/src/main/kotlin/transformers/pages/merger/SourceSetMergingPageTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/merger/SourceSetMergingPageTransformer.kt index 6cb7f603..f9da616c 100644 --- a/plugins/base/src/main/kotlin/transformers/pages/merger/SourceSetMergingPageTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/pages/merger/SourceSetMergingPageTransformer.kt @@ -3,7 +3,9 @@ package org.jetbrains.dokka.base.transformers.pages.merger import org.jetbrains.dokka.Platform import org.jetbrains.dokka.model.DisplaySourceSet import org.jetbrains.dokka.model.toDisplaySourceSets -import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.pages.ContentComposite +import org.jetbrains.dokka.pages.ContentNode +import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.transformers.pages.PageTransformer diff --git a/plugins/base/src/main/kotlin/transformers/pages/samples/DefaultSamplesTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/samples/DefaultSamplesTransformer.kt deleted file mode 100644 index 49ddd0a5..00000000 --- a/plugins/base/src/main/kotlin/transformers/pages/samples/DefaultSamplesTransformer.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.jetbrains.dokka.base.transformers.pages.samples - -import com.intellij.psi.PsiElement -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.kotlin.psi.KtBlockExpression -import org.jetbrains.kotlin.psi.KtDeclarationWithBody -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.utils.addToStdlib.safeAs - -class DefaultSamplesTransformer(context: DokkaContext) : SamplesTransformer(context) { - - override fun processBody(psiElement: PsiElement): String { - val text = processSampleBody(psiElement).trim { it == '\n' || it == '\r' }.trimEnd() - val lines = text.split("\n") - val indent = lines.filter(String::isNotBlank).map { it.takeWhile(Char::isWhitespace).count() }.minOrNull() ?: 0 - return lines.joinToString("\n") { it.drop(indent) } - } - - private fun processSampleBody(psiElement: PsiElement): String = when (psiElement) { - is KtDeclarationWithBody -> { - when (val bodyExpression = psiElement.bodyExpression) { - is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}") - else -> bodyExpression!!.text - } - } - else -> psiElement.text - } - - override fun processImports(psiElement: PsiElement): String { - val psiFile = psiElement.containingFile - return when(val text = psiFile.safeAs<KtFile>()?.importList?.text) { - is String -> text - else -> "" - } - } -}
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt deleted file mode 100644 index e72700e0..00000000 --- a/plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt +++ /dev/null @@ -1,148 +0,0 @@ -package org.jetbrains.dokka.base.transformers.pages.samples - -import com.intellij.psi.PsiElement -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.analysis.* -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.renderers.sourceSets -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.DisplaySourceSet -import org.jetbrains.dokka.model.doc.Sample -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.dokka.transformers.pages.PageTransformer -import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils - -internal const val KOTLIN_PLAYGROUND_SCRIPT = "<script src=\"https://unpkg.com/kotlin-playground@1\"></script>" -abstract class SamplesTransformer(val context: DokkaContext) : PageTransformer { - - abstract fun processBody(psiElement: PsiElement): String - abstract fun processImports(psiElement: PsiElement): String - - final override fun invoke(input: RootPageNode): RootPageNode = - /** - * Run from the thread of [Dispatchers.Default]. It can help to avoid a memory leaks in `ThreadLocal`s (that keep `URLCLassLoader`) - * since we shut down Dispatchers. Default at the end of each task (see [org.jetbrains.dokka.DokkaConfiguration.finalizeCoroutines]). - * Currently, all `ThreadLocal`s are in a compiler/IDE codebase. - */ - runBlocking(Dispatchers.Default) { - val analysis = SamplesKotlinAnalysis( - sourceSets = context.configuration.sourceSets, - logger = context.logger, - projectKotlinAnalysis = context.plugin<DokkaBase>().querySingle { kotlinAnalysis } - ) - analysis.use { - input.transformContentPagesTree { page -> - val samples = (page as? WithDocumentables)?.documentables?.flatMap { - it.documentation.entries.flatMap { entry -> - entry.value.children.filterIsInstance<Sample>().map { entry.key to it } - } - } - - samples?.fold(page as ContentPage) { acc, (sampleSourceSet, sample) -> - acc.modified( - content = acc.content.addSample(page, sampleSourceSet, sample.name, it), - embeddedResources = acc.embeddedResources + KOTLIN_PLAYGROUND_SCRIPT - ) - } ?: page - } - } - } - - private fun ContentNode.addSample( - contentPage: ContentPage, - sourceSet: DokkaSourceSet, - fqName: String, - analysis: KotlinAnalysis - ): ContentNode { - val facade = analysis[sourceSet].facade - val psiElement = fqNameToPsiElement(facade, fqName) - ?: return this.also { context.logger.warn("Cannot find PsiElement corresponding to $fqName") } - val imports = - processImports(psiElement) - val body = processBody(psiElement) - val node = contentCode(contentPage.sourceSets(), contentPage.dri, createSampleBody(imports, body), "kotlin") - - return dfs(fqName, node) - } - - protected open fun createSampleBody(imports: String, body: String) = - """ |$imports - |fun main() { - | //sampleStart - | $body - | //sampleEnd - |}""".trimMargin() - - private fun ContentNode.dfs(fqName: String, node: ContentCodeBlock): ContentNode { - return when (this) { - is ContentHeader -> copy(children.map { it.dfs(fqName, node) }) - is ContentDivergentGroup -> @Suppress("UNCHECKED_CAST") copy(children.map { - it.dfs(fqName, node) - } as List<ContentDivergentInstance>) - is ContentDivergentInstance -> copy( - before.let { it?.dfs(fqName, node) }, - divergent.dfs(fqName, node), - after.let { it?.dfs(fqName, node) }) - is ContentCodeBlock -> copy(children.map { it.dfs(fqName, node) }) - is ContentCodeInline -> copy(children.map { it.dfs(fqName, node) }) - is ContentDRILink -> copy(children.map { it.dfs(fqName, node) }) - is ContentResolvedLink -> copy(children.map { it.dfs(fqName, node) }) - is ContentEmbeddedResource -> copy(children.map { it.dfs(fqName, node) }) - is ContentTable -> copy(children = children.map { it.dfs(fqName, node) as ContentGroup }) - is ContentList -> copy(children.map { it.dfs(fqName, node) }) - is ContentGroup -> copy(children.map { it.dfs(fqName, node) }) - is PlatformHintedContent -> copy(inner.dfs(fqName, node)) - is ContentText -> if (text == fqName) node else this - is ContentBreakLine -> this - else -> this.also { context.logger.error("Could not recognize $this ContentNode in SamplesTransformer") } - } - } - - private fun fqNameToPsiElement(resolutionFacade: DokkaResolutionFacade, functionName: String): PsiElement? { - val packageName = functionName.takeWhile { it != '.' } - val descriptor = resolutionFacade.resolveSession.getPackageFragment(FqName(packageName)) - ?: return null.also { context.logger.warn("Cannot find descriptor for package $packageName") } - val symbol = resolveKDocLink( - BindingContext.EMPTY, - resolutionFacade, - descriptor, - null, - functionName.split(".") - ).firstOrNull() ?: return null.also { context.logger.warn("Unresolved function $functionName in @sample") } - return DescriptorToSourceUtils.descriptorToDeclaration(symbol) - } - - private fun contentCode( - sourceSets: Set<DisplaySourceSet>, - dri: Set<DRI>, - content: String, - language: String, - styles: Set<Style> = emptySet(), - extra: PropertyContainer<ContentNode> = PropertyContainer.empty() - ) = - ContentCodeBlock( - children = listOf( - ContentText( - text = content, - dci = DCI(dri, ContentKind.Sample), - sourceSets = sourceSets, - style = emptySet(), - extra = PropertyContainer.empty() - ) - ), - language = language, - dci = DCI(dri, ContentKind.Sample), - sourceSets = sourceSets, - style = styles + ContentStyle.RunnableSample + TextStyle.Monospace, - extra = extra - ) -} diff --git a/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt index 27428fc6..f2c3d3f0 100644 --- a/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt @@ -1,26 +1,19 @@ package org.jetbrains.dokka.base.transformers.pages.sourcelinks -import com.intellij.psi.PsiDocumentManager -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiIdentifier import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.analysis.DescriptorDocumentableSource -import org.jetbrains.dokka.analysis.PsiDocumentableSource import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.DocumentableSource +import org.jetbrains.dokka.model.WithSources +import org.jetbrains.dokka.model.sourceSetIDs import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.plugin import org.jetbrains.dokka.plugability.querySingle import org.jetbrains.dokka.transformers.pages.PageTransformer -import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource -import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.psiUtil.getChildOfType -import org.jetbrains.kotlin.resolve.source.getPsi -import org.jetbrains.kotlin.utils.addToStdlib.cast import java.io.File class SourceLinksTransformer(val context: DokkaContext) : PageTransformer { @@ -80,31 +73,13 @@ class SourceLinksTransformer(val context: DokkaContext) : PageTransformer { val sourcePath = File(this.path).invariantSeparatorsPath val sourceLinkPath = File(sourceLink.path).invariantSeparatorsPath - val lineNumber = when (this) { - is DescriptorDocumentableSource -> this.descriptor - .cast<DeclarationDescriptorWithSource>() - .source.getPsi() - ?.lineNumber() - is PsiDocumentableSource -> this.psi.lineNumber() - else -> null - } + val lineNumber = this.computeLineNumber() return sourceLink.url + sourcePath.split(sourceLinkPath)[1] + sourceLink.lineSuffix + "${lineNumber ?: 1}" } - private fun PsiElement.lineNumber(): Int? { - val ktIdentifierTextRange = this.node?.findChildByType(KtTokens.IDENTIFIER)?.textRange - val javaIdentifierTextRange = this.getChildOfType<PsiIdentifier>()?.textRange - // synthetic and some light methods might return null - val textRange = ktIdentifierTextRange ?: javaIdentifierTextRange ?: textRange ?: return null - - val doc = PsiDocumentManager.getInstance(project).getDocument(containingFile) - // IJ uses 0-based line-numbers; external source browsers use 1-based - return doc?.getLineNumber(textRange.startOffset)?.plus(1) - } - private fun ContentNode.signatureGroupOrNull() = (this as? ContentGroup)?.takeIf { it.dci.kind == ContentKind.Symbol } @@ -150,4 +125,4 @@ data class SourceLink(val path: String, val url: String, val lineSuffix: String? sourceLinkDefinition.remoteLineSuffix, sourceSetData ) -}
\ No newline at end of file +} diff --git a/plugins/base/src/main/kotlin/translators/annotationsValue.kt b/plugins/base/src/main/kotlin/translators/annotationsValue.kt deleted file mode 100644 index a840816a..00000000 --- a/plugins/base/src/main/kotlin/translators/annotationsValue.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.jetbrains.dokka.base.translators - -internal fun unquotedValue(value: String): String = value.removeSurrounding("\"")
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt deleted file mode 100644 index ce4776e7..00000000 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt +++ /dev/null @@ -1,1250 +0,0 @@ -package org.jetbrains.dokka.base.translators.descriptors - -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiNamedElement -import com.intellij.psi.util.PsiLiteralUtil.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.runBlocking -import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.analysis.DescriptorDocumentableSource -import org.jetbrains.dokka.analysis.DokkaResolutionFacade -import org.jetbrains.dokka.analysis.KotlinAnalysis -import org.jetbrains.dokka.analysis.from -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.parsers.MarkdownParser -import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser -import org.jetbrains.dokka.base.translators.typeConstructorsBeingExceptions -import org.jetbrains.dokka.base.translators.unquotedValue -import org.jetbrains.dokka.links.* -import org.jetbrains.dokka.links.Callable -import org.jetbrains.dokka.model.* -import org.jetbrains.dokka.model.AnnotationTarget -import org.jetbrains.dokka.model.Nullable -import org.jetbrains.dokka.model.Visibility -import org.jetbrains.dokka.model.doc.* -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.dokka.transformers.sources.AsyncSourceToDocumentableTranslator -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.dokka.utilities.parallelMap -import org.jetbrains.dokka.utilities.parallelMapNotNull -import org.jetbrains.kotlin.KtNodeTypes -import org.jetbrains.kotlin.builtins.functions.FunctionClassDescriptor -import org.jetbrains.kotlin.builtins.isBuiltinExtensionFunctionalType -import org.jetbrains.kotlin.builtins.isExtensionFunctionType -import org.jetbrains.kotlin.builtins.isSuspendFunctionTypeOrSubtype -import org.jetbrains.kotlin.codegen.isJvmStaticInObjectOrClassOrInterface -import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.annotations.Annotated -import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor -import org.jetbrains.kotlin.descriptors.java.JavaVisibilities -import org.jetbrains.kotlin.idea.kdoc.findKDoc -import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink -import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi -import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor -import org.jetbrains.kotlin.load.kotlin.toSourceElement -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils -import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.calls.components.isVararg -import org.jetbrains.kotlin.resolve.calls.util.getValueArgumentsInParentheses -import org.jetbrains.kotlin.resolve.constants.ConstantValue -import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.LocalClass -import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.NormalClass -import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull -import org.jetbrains.kotlin.resolve.descriptorUtil.parents -import org.jetbrains.kotlin.resolve.scopes.MemberScope -import org.jetbrains.kotlin.resolve.scopes.StaticScopeForKotlinEnum -import org.jetbrains.kotlin.resolve.source.KotlinSourceElement -import org.jetbrains.kotlin.resolve.source.PsiSourceElement -import org.jetbrains.kotlin.resolve.source.PsiSourceFile -import org.jetbrains.kotlin.types.* -import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes -import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull -import org.jetbrains.kotlin.utils.addToStdlib.safeAs -import java.nio.file.Paths -import org.jetbrains.kotlin.resolve.constants.AnnotationValue as ConstantsAnnotationValue -import org.jetbrains.kotlin.resolve.constants.ArrayValue as ConstantsArrayValue -import org.jetbrains.kotlin.resolve.constants.BooleanValue as ConstantsBooleanValue -import org.jetbrains.kotlin.resolve.constants.DoubleValue as ConstantsDoubleValue -import org.jetbrains.kotlin.resolve.constants.EnumValue as ConstantsEnumValue -import org.jetbrains.kotlin.resolve.constants.FloatValue as ConstantsFloatValue -import org.jetbrains.kotlin.resolve.constants.IntValue as ConstantsIntValue -import org.jetbrains.kotlin.resolve.constants.KClassValue as ConstantsKtClassValue -import org.jetbrains.kotlin.resolve.constants.LongValue as ConstantsLongValue -import org.jetbrains.kotlin.resolve.constants.NullValue as ConstantsNullValue -import org.jetbrains.kotlin.resolve.constants.UIntValue as ConstantsUIntValue -import org.jetbrains.kotlin.resolve.constants.ULongValue as ConstantsULongValue - -class DefaultDescriptorToDocumentableTranslator( - private val context: DokkaContext -) : AsyncSourceToDocumentableTranslator, ExternalClasslikesTranslator { - - private val kotlinAnalysis: KotlinAnalysis = context.plugin<DokkaBase>().querySingle { kotlinAnalysis } - - override suspend fun invokeSuspending(sourceSet: DokkaSourceSet, context: DokkaContext): DModule { - val (environment, facade) = kotlinAnalysis[sourceSet] - val packageFragments = environment.getSourceFiles().asSequence() - .map { it.packageFqName } - .distinct() - .mapNotNull { facade.resolveSession.getPackageFragment(it) } - .toList() - - return DokkaDescriptorVisitor(sourceSet, kotlinAnalysis[sourceSet].facade, context.logger).run { - packageFragments.mapNotNull { it.safeAs<PackageFragmentDescriptor>() }.parallelMap { - visitPackageFragmentDescriptor( - it - ) - } - }.let { - DModule( - name = context.configuration.moduleName, - packages = it, - documentation = emptyMap(), - expectPresentInSet = null, - sourceSets = setOf(sourceSet) - ) - } - } - - override fun translateClassDescriptor(descriptor: ClassDescriptor, sourceSet: DokkaSourceSet): DClasslike { - val driInfo = DRI.from(descriptor.parents.first()).withEmptyInfo() - - return runBlocking(Dispatchers.Default) { - DokkaDescriptorVisitor(sourceSet, kotlinAnalysis[sourceSet].facade, context.logger) - .visitClassDescriptor(descriptor, driInfo) - } - } -} - -data class DRIWithPlatformInfo( - val dri: DRI, - val actual: SourceSetDependent<DocumentableSource> -) - -fun DRI.withEmptyInfo() = DRIWithPlatformInfo(this, emptyMap()) - -private class DokkaDescriptorVisitor( - private val sourceSet: DokkaSourceSet, - private val resolutionFacade: DokkaResolutionFacade, - private val logger: DokkaLogger -) { - private val javadocParser = JavadocParser(logger, resolutionFacade) - private val syntheticDocProvider = SyntheticDescriptorDocumentationProvider(resolutionFacade) - - private fun Collection<DeclarationDescriptor>.filterDescriptorsInSourceSet() = filter { - it.toSourceElement.containingFile.toString().let { path -> - path.isNotBlank() && sourceSet.sourceRoots.any { root -> - Paths.get(path).startsWith(root.toPath()) - } - } - } - - private fun <T> T.toSourceSetDependent() = if (this != null) mapOf(sourceSet to this) else emptyMap() - - suspend fun visitPackageFragmentDescriptor(descriptor: PackageFragmentDescriptor): DPackage { - val name = descriptor.fqName.asString().takeUnless { it.isBlank() } ?: "" - val driWithPlatform = DRI(packageName = name).withEmptyInfo() - val scope = descriptor.getMemberScope() - return coroutineScope { - val descriptorsWithKind = scope.getDescriptorsWithKind(true) - - val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) } - val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) } - val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) } - val typealiases = async { descriptorsWithKind.typealiases.visitTypealiases() } - - DPackage( - dri = driWithPlatform.dri, - functions = functions.await(), - properties = properties.await(), - classlikes = classlikes.await(), - typealiases = typealiases.await(), - documentation = descriptor.resolveDescriptorData(), - sourceSets = setOf(sourceSet) - ) - } - } - - suspend fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DClasslike = - when (descriptor.kind) { - ClassKind.ENUM_CLASS -> enumDescriptor(descriptor, parent) - ClassKind.OBJECT -> objectDescriptor(descriptor, parent) - ClassKind.INTERFACE -> interfaceDescriptor(descriptor, parent) - ClassKind.ANNOTATION_CLASS -> annotationDescriptor(descriptor, parent) - else -> classDescriptor(descriptor, parent) - } - - private suspend fun interfaceDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DInterface { - val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() - val scope = descriptor.unsubstitutedMemberScope - val isExpect = descriptor.isExpect - val isActual = descriptor.isActual - val info = descriptor.resolveClassDescriptionData() - - return coroutineScope { - val descriptorsWithKind = scope.getDescriptorsWithKind() - - val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) } - val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) } - val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) } - val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } } - - DInterface( - dri = driWithPlatform.dri, - name = descriptor.name.asString(), - functions = functions.await(), - properties = properties.await(), - classlikes = classlikes.await(), - sources = descriptor.createSources(), - expectPresentInSet = sourceSet.takeIf { isExpect }, - visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), - supertypes = info.supertypes.toSourceSetDependent(), - documentation = info.docs, - generics = generics.await(), - companion = descriptor.companion(driWithPlatform), - sourceSets = setOf(sourceSet), - isExpectActual = (isExpect || isActual), - extra = PropertyContainer.withAll( - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), - info.ancestry.exceptionInSupertypesOrNull() - ) - ) - } - } - - private suspend fun objectDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DObject { - val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() - val scope = descriptor.unsubstitutedMemberScope - val isExpect = descriptor.isExpect - val isActual = descriptor.isActual - val info = descriptor.resolveClassDescriptionData() - - - return coroutineScope { - val descriptorsWithKind = scope.getDescriptorsWithKind() - - val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) } - val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) } - val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) } - - DObject( - dri = driWithPlatform.dri, - name = descriptor.name.asString(), - functions = functions.await(), - properties = properties.await(), - classlikes = classlikes.await(), - sources = descriptor.createSources(), - expectPresentInSet = sourceSet.takeIf { isExpect }, - visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), - supertypes = info.supertypes.toSourceSetDependent(), - documentation = info.docs, - sourceSets = setOf(sourceSet), - isExpectActual = (isExpect || isActual), - extra = PropertyContainer.withAll( - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), - info.ancestry.exceptionInSupertypesOrNull() - ) - ) - } - - - } - - private suspend fun enumDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DEnum { - val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() - val isExpect = descriptor.isExpect - val isActual = descriptor.isActual - val info = descriptor.resolveClassDescriptionData() - - return coroutineScope { - val descriptorsWithKind = descriptor.getEnumDescriptorsWithKind() - - val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) } - val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) } - val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) } - val constructors = - async { descriptor.constructors.parallelMap { visitConstructorDescriptor(it, driWithPlatform) } } - val entries = async { descriptorsWithKind.enumEntries.visitEnumEntries(driWithPlatform) } - - DEnum( - dri = driWithPlatform.dri, - name = descriptor.name.asString(), - entries = entries.await(), - constructors = constructors.await(), - functions = functions.await(), - properties = properties.await(), - classlikes = classlikes.await(), - sources = descriptor.createSources(), - expectPresentInSet = sourceSet.takeIf { isExpect }, - visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), - supertypes = info.supertypes.toSourceSetDependent(), - documentation = info.docs, - companion = descriptor.companion(driWithPlatform), - sourceSets = setOf(sourceSet), - isExpectActual = (isExpect || isActual), - extra = PropertyContainer.withAll( - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()) - ) - ) - } - } - - private fun ClassDescriptor.getEnumDescriptorsWithKind(): DescriptorsWithKind { - val descriptorsWithKind = this.unsubstitutedMemberScope.getDescriptorsWithKind() - val staticScopeForKotlinEnum = (this.staticScope as? StaticScopeForKotlinEnum) ?: return descriptorsWithKind - - // synthetic values() and valueOf() functions are not present among average class functions - val enumSyntheticFunctions = staticScopeForKotlinEnum.getContributedDescriptors { true } - .filterIsInstance<FunctionDescriptor>() - - return descriptorsWithKind.copy(functions = descriptorsWithKind.functions + enumSyntheticFunctions) - } - - private suspend fun visitEnumEntryDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DEnumEntry { - val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() - val scope = descriptor.unsubstitutedMemberScope - val isExpect = descriptor.isExpect - - return coroutineScope { - val descriptorsWithKind = scope.getDescriptorsWithKind() - - val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) } - val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) } - val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) } - - DEnumEntry( - dri = driWithPlatform.dri.withEnumEntryExtra(), - name = descriptor.name.asString(), - documentation = descriptor.resolveDescriptorData(), - functions = functions.await(), - properties = properties.await(), - classlikes = classlikes.await(), - sourceSets = setOf(sourceSet), - expectPresentInSet = sourceSet.takeIf { isExpect }, - extra = PropertyContainer.withAll( - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations() - ) - ) - } - } - - private suspend fun annotationDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DAnnotation { - val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() - val scope = descriptor.unsubstitutedMemberScope - val isExpect = descriptor.isExpect - val isActual = descriptor.isActual - - return coroutineScope { - val descriptorsWithKind = scope.getDescriptorsWithKind() - - val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) } - val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) } - val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) } - val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } } - val constructors = - async { descriptor.constructors.parallelMap { visitConstructorDescriptor(it, driWithPlatform) } } - - DAnnotation( - dri = driWithPlatform.dri, - name = descriptor.name.asString(), - documentation = descriptor.resolveDescriptorData(), - functions = functions.await(), - properties = properties.await(), - classlikes = classlikes.await(), - expectPresentInSet = sourceSet.takeIf { isExpect }, - sourceSets = setOf(sourceSet), - isExpectActual = (isExpect || isActual), - extra = PropertyContainer.withAll( - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations() - ), - companion = descriptor.companionObjectDescriptor?.let { objectDescriptor(it, driWithPlatform) }, - visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), - generics = generics.await(), - constructors = constructors.await(), - sources = descriptor.createSources() - ) - } - - - } - - private suspend fun classDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DClass { - val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() - val scope = descriptor.unsubstitutedMemberScope - val isExpect = descriptor.isExpect - val isActual = descriptor.isActual - val info = descriptor.resolveClassDescriptionData() - val actual = descriptor.createSources() - - return coroutineScope { - val descriptorsWithKind = scope.getDescriptorsWithKind() - - val (regularFunctions, accessors) = splitFunctionsAndInheritedAccessors( - properties = descriptorsWithKind.properties, - functions = descriptorsWithKind.functions - ) - - val functions = async { regularFunctions.visitFunctions(driWithPlatform) } - val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform, accessors) } - val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) } - val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } } - val constructors = async { - descriptor.constructors.parallelMap { - visitConstructorDescriptor( - it, - if (it.isPrimary) DRIWithPlatformInfo(driWithPlatform.dri, actual) - else DRIWithPlatformInfo(driWithPlatform.dri, emptyMap()) - ) - } - } - - DClass( - dri = driWithPlatform.dri, - name = descriptor.name.asString(), - constructors = constructors.await(), - functions = functions.await(), - properties = properties.await(), - classlikes = classlikes.await(), - sources = actual, - expectPresentInSet = sourceSet.takeIf { isExpect }, - visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), - supertypes = info.supertypes.toSourceSetDependent(), - generics = generics.await(), - documentation = info.docs, - modifier = descriptor.modifier().toSourceSetDependent(), - companion = descriptor.companion(driWithPlatform), - sourceSets = setOf(sourceSet), - isExpectActual = (isExpect || isActual), - extra = PropertyContainer.withAll( - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), - info.ancestry.exceptionInSupertypesOrNull() - ) - ) - } - } - - /** - * @param implicitAccessors getters/setters that are not part of the property descriptor, for instance - * average methods inherited from java sources that access the property - */ - private suspend fun visitPropertyDescriptor( - originalDescriptor: PropertyDescriptor, - implicitAccessors: DescriptorAccessorHolder?, - parent: DRIWithPlatformInfo - ): DProperty { - val (dri, _) = originalDescriptor.createDRI() - val inheritedFrom = dri.getInheritedFromDRI(parent) - val descriptor = originalDescriptor.getConcreteDescriptor() - val isExpect = descriptor.isExpect - val isActual = descriptor.isActual - - val actual = originalDescriptor.createSources() - - // example - generated getter that comes with data classes - suspend fun getDescriptorGetter() = - descriptor.accessors - .firstIsInstanceOrNull<PropertyGetterDescriptor>() - ?.let { - visitPropertyAccessorDescriptor(it, descriptor, dri, inheritedFrom) - } - - suspend fun getImplicitAccessorGetter() = - implicitAccessors?.getter?.let { visitFunctionDescriptor(it, parent) } - - // example - generated setter that comes with data classes - suspend fun getDescriptorSetter() = - descriptor.accessors - .firstIsInstanceOrNull<PropertySetterDescriptor>() - ?.let { - visitPropertyAccessorDescriptor(it, descriptor, dri, inheritedFrom) - } - - suspend fun getImplicitAccessorSetter() = - implicitAccessors?.setter?.let { visitFunctionDescriptor(it, parent) } - - return coroutineScope { - val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } } - val getter = getDescriptorGetter() ?: getImplicitAccessorGetter() - val setter = getDescriptorSetter() ?: getImplicitAccessorSetter() - - DProperty( - dri = dri, - name = descriptor.name.asString(), - receiver = descriptor.extensionReceiverParameter?.let { - visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual)) - }, - sources = actual, - getter = getter, - setter = setter, - visibility = descriptor.getVisibility(implicitAccessors).toSourceSetDependent(), - documentation = descriptor.resolveDescriptorData(), - modifier = descriptor.modifier().toSourceSetDependent(), - type = descriptor.returnType!!.toBound(), - expectPresentInSet = sourceSet.takeIf { isExpect }, - sourceSets = setOf(sourceSet), - generics = generics.await(), - isExpectActual = (isExpect || isActual), - extra = PropertyContainer.withAll( - listOfNotNull( - (descriptor.additionalExtras() + descriptor.getAnnotationsWithBackingField() - .toAdditionalExtras()).toSet().toSourceSetDependent().toAdditionalModifiers(), - (descriptor.getAnnotationsWithBackingField() + descriptor.fileLevelAnnotations()).toSourceSetDependent() - .toAnnotations(), - descriptor.getDefaultValue()?.let { DefaultValue(it.toSourceSetDependent()) }, - inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) }, - takeIf { descriptor.isVar(getter, setter) }?.let { IsVar }, - ) - ) - ) - } - } - - private fun PropertyDescriptor.isVar(getter: DFunction?, setter: DFunction?): Boolean { - return if (this is JavaPropertyDescriptor) { - // in Java, concepts of extensibility and mutability are mixed into a single `final` modifier - // in Kotlin, it's different - val/var controls mutability and open modifier controls extensibility - // so when inheriting Java properties, you can end up with a final var - non extensible mutable prop - val isMutable = this.isVar - // non-final java property should be var if it has no accessors at all or has a setter - (isMutable && getter == null && setter == null) || (getter != null && setter != null) - } else { - this.isVar - } - } - - private fun PropertyDescriptor.getVisibility(implicitAccessors: DescriptorAccessorHolder?): Visibility { - val isNonPublicJavaProperty = this is JavaPropertyDescriptor && !this.visibility.isPublicAPI - val visibility = - if (isNonPublicJavaProperty) { - // only try to take implicit getter's visibility if it's a java property - // because it's not guaranteed that implicit accessor will be used - // for the kotlin property, as it may have an explicit accessor of its own, - // i.e in data classes or with get() and set() are overridden - (implicitAccessors?.getter?.visibility ?: this.visibility) - } else { - this.visibility - } - - return visibility.toDokkaVisibility() - } - - private fun CallableMemberDescriptor.createDRI(wasOverridenBy: DRI? = null): Pair<DRI, DRI?> = - if (kind == CallableMemberDescriptor.Kind.DECLARATION || overriddenDescriptors.isEmpty()) - Pair(DRI.from(this), wasOverridenBy) - else - overriddenDescriptors.first().createDRI(DRI.from(this)) - - private suspend fun visitFunctionDescriptor( - originalDescriptor: FunctionDescriptor, - parent: DRIWithPlatformInfo - ): DFunction { - val (dri, _) = originalDescriptor.createDRI() - val inheritedFrom = dri.getInheritedFromDRI(parent) - val descriptor = originalDescriptor.getConcreteDescriptor() - val isExpect = descriptor.isExpect - val isActual = descriptor.isActual - - val actual = originalDescriptor.createSources() - return coroutineScope { - val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } } - - DFunction( - dri = dri, - name = descriptor.name.asString(), - isConstructor = false, - receiver = descriptor.extensionReceiverParameter?.let { - visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual)) - }, - parameters = descriptor.valueParameters.mapIndexed { index, desc -> - parameter(index, desc, DRIWithPlatformInfo(dri, actual)) - }, - expectPresentInSet = sourceSet.takeIf { isExpect }, - sources = actual, - visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), - generics = generics.await(), - documentation = descriptor.getDocumentation(), - modifier = descriptor.modifier().toSourceSetDependent(), - type = descriptor.returnType!!.toBound(), - sourceSets = setOf(sourceSet), - isExpectActual = (isExpect || isActual), - extra = PropertyContainer.withAll( - inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) }, - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - (descriptor.getAnnotations() + descriptor.fileLevelAnnotations()).toSourceSetDependent() - .toAnnotations(), - ObviousMember.takeIf { descriptor.isObvious() }, - ) - ) - } - } - - private fun FunctionDescriptor.getDocumentation(): SourceSetDependent<DocumentationNode> { - val isSynthesized = this.kind == CallableMemberDescriptor.Kind.SYNTHESIZED - return if (isSynthesized) { - syntheticDocProvider.getDocumentation(this)?.toSourceSetDependent() ?: emptyMap() - } else { - this.resolveDescriptorData() - } - } - - /** - * `createDRI` returns the DRI of the exact element and potential DRI of an element that is overriding it - * (It can be also FAKE_OVERRIDE which is in fact just inheritance of the symbol) - * - * Looking at what PSIs do, they give the DRI of the element within the classnames where it is actually - * declared and inheritedFrom as the same DRI but truncated callable part. - * Therefore, we set callable to null and take the DRI only if it is indeed coming from different class. - */ - private fun DRI.getInheritedFromDRI(parent: DRIWithPlatformInfo): DRI? { - return this.copy(callable = null) - .takeIf { parent.dri.classNames != this.classNames || parent.dri.packageName != this.packageName } - } - - private fun FunctionDescriptor.isObvious(): Boolean { - return kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE - || (kind == CallableMemberDescriptor.Kind.SYNTHESIZED && !syntheticDocProvider.isDocumented(this)) - || containingDeclaration.fqNameOrNull()?.isObvious() == true - } - - private fun FqName.isObvious(): Boolean = with(this.asString()) { - return this == "kotlin.Any" || this == "kotlin.Enum" - || this == "java.lang.Object" || this == "java.lang.Enum" - } - - suspend fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, parent: DRIWithPlatformInfo): DFunction { - val name = descriptor.constructedClass.name.toString() - val dri = parent.dri.copy(callable = Callable.from(descriptor, name)) - val actual = descriptor.createSources() - val isExpect = descriptor.isExpect - val isActual = descriptor.isActual - - return coroutineScope { - val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } } - - DFunction( - dri = dri, - name = name, - isConstructor = true, - receiver = descriptor.extensionReceiverParameter?.let { - visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual)) - }, - parameters = descriptor.valueParameters.mapIndexed { index, desc -> - parameter(index, desc, DRIWithPlatformInfo(dri, actual)) - }, - sources = actual, - expectPresentInSet = sourceSet.takeIf { isExpect }, - visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), - documentation = descriptor.resolveDescriptorData().let { sourceSetDependent -> - if (descriptor.isPrimary) { - sourceSetDependent.map { entry -> - Pair( - entry.key, - entry.value.copy(children = (entry.value.children.find { it is Constructor }?.root?.let { constructor -> - listOf(Description(constructor)) - } ?: emptyList<TagWrapper>()) + entry.value.children.filterIsInstance<Param>())) - }.toMap() - } else { - sourceSetDependent - } - }, - type = descriptor.returnType.toBound(), - modifier = descriptor.modifier().toSourceSetDependent(), - generics = generics.await(), - sourceSets = setOf(sourceSet), - isExpectActual = (isExpect || isActual), - extra = PropertyContainer.withAll<DFunction>( - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations() - ).let { - if (descriptor.isPrimary) { - it + PrimaryConstructorExtra - } else it - } - ) - } - } - - private suspend fun visitReceiverParameterDescriptor( - descriptor: ReceiverParameterDescriptor, - parent: DRIWithPlatformInfo - ) = DParameter( - dri = parent.dri.copy(target = PointingToDeclaration), - name = null, - type = descriptor.type.toBound(), - expectPresentInSet = null, - documentation = descriptor.resolveDescriptorData(), - sourceSets = setOf(sourceSet), - extra = PropertyContainer.withAll(descriptor.getAnnotations().toSourceSetDependent().toAnnotations()) - ) - - private suspend fun visitPropertyAccessorDescriptor( - descriptor: PropertyAccessorDescriptor, - propertyDescriptor: PropertyDescriptor, - parent: DRI, - inheritedFrom: DRI? = null - ): DFunction { - val dri = parent.copy(callable = Callable.from(descriptor)) - val isGetter = descriptor is PropertyGetterDescriptor - val isExpect = descriptor.isExpect - val isActual = descriptor.isActual - - suspend fun PropertyDescriptor.asParameter(parent: DRI) = - DParameter( - parent.copy(target = PointingToCallableParameters(parameterIndex = 1)), - this.name.asString(), - type = this.type.toBound(), - expectPresentInSet = sourceSet.takeIf { isExpect }, - documentation = descriptor.resolveDescriptorData(), - sourceSets = setOf(sourceSet), - extra = PropertyContainer.withAll( - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - getAnnotationsWithBackingField().toSourceSetDependent().toAnnotations() - ) - ) - - val name = run { - val rawName = propertyDescriptor.name.asString() - /* - * Kotlin has special rules for conversion around properties that - * start with "is" For more info see: - * https://kotlinlang.org/docs/java-interop.html#getters-and-setters - * https://kotlinlang.org/docs/java-to-kotlin-interop.html#properties - * - * Based on our testing, this rule only applies when the letter after - * the "is" is *not* lowercase. This means that words like "issue" won't - * have the rule applied but "is_foobar" and "is1of" will have the rule applied. - */ - val specialCaseIs = rawName.startsWith("is") - && rawName.getOrNull(2)?.isLowerCase() == false - - if (specialCaseIs) { - if (isGetter) rawName else rawName.replaceFirst("is", "set") - } else { - if (isGetter) "get${rawName.capitalize()}" else "set${rawName.capitalize()}" - } - } - - val parameters = - if (isGetter) { - emptyList() - } else { - listOf(propertyDescriptor.asParameter(dri)) - } - - return coroutineScope { - val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } } - DFunction( - dri, - name, - isConstructor = false, - parameters = parameters, - visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), - documentation = descriptor.resolveDescriptorData().mapInheritedTagWrappers(), - type = descriptor.returnType!!.toBound(), - generics = generics.await(), - modifier = descriptor.modifier().toSourceSetDependent(), - expectPresentInSet = sourceSet.takeIf { isExpect }, - receiver = descriptor.extensionReceiverParameter?.let { - visitReceiverParameterDescriptor( - it, - DRIWithPlatformInfo(dri, descriptor.createSources()) - ) - }, - sources = descriptor.createSources(), - sourceSets = setOf(sourceSet), - isExpectActual = (isExpect || isActual), - extra = PropertyContainer.withAll( - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) } - ) - ) - } - } - - /** - * Workaround for a problem with inheriting parent TagWrappers of the wrong type. - * - * For instance, if you annotate a class with `@property`, kotlin compiler will propagate - * this tag to the property and its getters and setters. In case of getters and setters, - * it's more correct to display propagated docs as description instead of property - */ - private fun SourceSetDependent<DocumentationNode>.mapInheritedTagWrappers(): SourceSetDependent<DocumentationNode> { - return this.mapValues { (_, value) -> - val mappedChildren = value.children.map { - when (it) { - is Property -> Description(it.root) - else -> it - } - } - value.copy(children = mappedChildren) - } - } - - private suspend fun visitTypeAliasDescriptor(descriptor: TypeAliasDescriptor) = - with(descriptor) { - coroutineScope { - val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } } - val info = buildAncestryInformation(defaultType).copy( - superclass = buildAncestryInformation(underlyingType), - interfaces = emptyList() - ) - DTypeAlias( - dri = DRI.from(this@with), - name = name.asString(), - type = defaultType.toBound(), - expectPresentInSet = null, - underlyingType = underlyingType.toBound().toSourceSetDependent(), - visibility = visibility.toDokkaVisibility().toSourceSetDependent(), - documentation = resolveDescriptorData(), - sourceSets = setOf(sourceSet), - generics = generics.await(), - sources = descriptor.createSources(), - extra = PropertyContainer.withAll( - descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - info.exceptionInSupertypesOrNull(), - ) - ) - } - } - - private suspend fun parameter(index: Int, descriptor: ValueParameterDescriptor, parent: DRIWithPlatformInfo) = - DParameter( - dri = parent.dri.copy(target = PointingToCallableParameters(index)), - name = descriptor.name.asString(), - type = descriptor.varargElementType?.toBound() ?: descriptor.type.toBound(), - expectPresentInSet = null, - documentation = descriptor.resolveDescriptorData(), - sourceSets = setOf(sourceSet), - extra = PropertyContainer.withAll(listOfNotNull( - descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - descriptor.getDefaultValue()?.let { DefaultValue(it.toSourceSetDependent()) } - )) - ) - - private data class DescriptorsWithKind( - val functions: List<FunctionDescriptor>, - val properties: List<PropertyDescriptor>, - val classlikes: List<ClassDescriptor>, - val typealiases: List<TypeAliasDescriptor>, - val enumEntries: List<ClassDescriptor> - ) - - private fun MemberScope.getDescriptorsWithKind(shouldFilter: Boolean = false): DescriptorsWithKind { - val descriptors = getContributedDescriptors { true }.let { - if (shouldFilter) it.filterDescriptorsInSourceSet() else it - } - - class EnumEntryDescriptor - - val groupedDescriptors = descriptors.groupBy { - when { - it is FunctionDescriptor -> FunctionDescriptor::class - it is PropertyDescriptor -> PropertyDescriptor::class - it is ClassDescriptor && it.kind != ClassKind.ENUM_ENTRY -> ClassDescriptor::class - it is TypeAliasDescriptor -> TypeAliasDescriptor::class - it is ClassDescriptor && it.kind == ClassKind.ENUM_ENTRY -> EnumEntryDescriptor::class - else -> IllegalStateException::class - } - } - - @Suppress("UNCHECKED_CAST") - return DescriptorsWithKind( - (groupedDescriptors[FunctionDescriptor::class] ?: emptyList()) as List<FunctionDescriptor>, - (groupedDescriptors[PropertyDescriptor::class] ?: emptyList()) as List<PropertyDescriptor>, - (groupedDescriptors[ClassDescriptor::class] ?: emptyList()) as List<ClassDescriptor>, - (groupedDescriptors[TypeAliasDescriptor::class] ?: emptyList()) as List<TypeAliasDescriptor>, - (groupedDescriptors[EnumEntryDescriptor::class] ?: emptyList()) as List<ClassDescriptor> - ) - } - - private suspend fun List<FunctionDescriptor>.visitFunctions(parent: DRIWithPlatformInfo): List<DFunction> = - coroutineScope { parallelMap { visitFunctionDescriptor(it, parent) } } - - private suspend fun List<PropertyDescriptor>.visitProperties( - parent: DRIWithPlatformInfo, - implicitAccessors: Map<PropertyDescriptor, DescriptorAccessorHolder> = emptyMap(), - ): List<DProperty> { - return coroutineScope { - parallelMap { - visitPropertyDescriptor(it, implicitAccessors[it], parent) - } - } - } - - private suspend fun List<ClassDescriptor>.visitClasslikes(parent: DRIWithPlatformInfo): List<DClasslike> = - coroutineScope { parallelMap { visitClassDescriptor(it, parent) } } - - private suspend fun List<TypeAliasDescriptor>.visitTypealiases(): List<DTypeAlias> = - coroutineScope { parallelMap { visitTypeAliasDescriptor(it) } } - - private suspend fun List<ClassDescriptor>.visitEnumEntries(parent: DRIWithPlatformInfo): List<DEnumEntry> = - coroutineScope { parallelMap { visitEnumEntryDescriptor(it, parent) } } - - private fun DeclarationDescriptor.resolveDescriptorData(): SourceSetDependent<DocumentationNode> = - getDocumentation()?.toSourceSetDependent() ?: emptyMap() - - - private suspend fun toTypeConstructor(kt: KotlinType) = - GenericTypeConstructor( - DRI.from(kt.constructor.declarationDescriptor as DeclarationDescriptor), - kt.arguments.map { it.toProjection() }, - extra = PropertyContainer.withAll(kt.getAnnotations().toSourceSetDependent().toAnnotations()) - ) - - private suspend fun buildAncestryInformation( - kotlinType: KotlinType - ): AncestryNode { - val (interfaces, superclass) = kotlinType.immediateSupertypes().filterNot { it.isAnyOrNullableAny() } - .partition { - val declaration = it.constructor.declarationDescriptor - val descriptor = declaration as? ClassDescriptor - ?: (declaration as? TypeAliasDescriptor)?.underlyingType?.constructor?.declarationDescriptor as? ClassDescriptor - descriptor?.kind == ClassKind.INTERFACE - } - - return coroutineScope { - AncestryNode( - typeConstructor = toTypeConstructor(kotlinType), - superclass = superclass.parallelMap(::buildAncestryInformation).singleOrNull(), - interfaces = interfaces.parallelMap(::buildAncestryInformation) - ) - } - } - - - private suspend fun ClassDescriptor.resolveClassDescriptionData(): ClassInfo { - return coroutineScope { - ClassInfo( - buildAncestryInformation(this@resolveClassDescriptionData.defaultType), - resolveDescriptorData() - ) - } - } - - private suspend fun TypeParameterDescriptor.toVariantTypeParameter() = - DTypeParameter( - variantTypeParameter( - TypeParameter(DRI.from(this), name.identifier, annotations.getPresentableName()) - ), - resolveDescriptorData(), - null, - upperBounds.map { it.toBound() }, - setOf(sourceSet), - extra = PropertyContainer.withAll( - additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - getAnnotations().toSourceSetDependent().toAnnotations() - ) - ) - - private fun org.jetbrains.kotlin.descriptors.annotations.Annotations.getPresentableName(): String? = - mapNotNull { it.toAnnotation() }.singleOrNull { it.dri.classNames == "ParameterName" }?.params?.get("name") - .safeAs<StringValue>()?.value?.let { unquotedValue(it) } - - private suspend fun KotlinType.toBound(): Bound { - suspend fun <T : AnnotationTarget> annotations(): PropertyContainer<T> = - getAnnotations().takeIf { it.isNotEmpty() }?.let { annotations -> - PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations()) - } ?: PropertyContainer.empty() - - return when (this) { - is DynamicType -> Dynamic - is AbbreviatedType -> TypeAliased( - abbreviation.toBound(), - expandedType.toBound(), - annotations() - ) - is DefinitelyNotNullType -> DefinitelyNonNullable( - original.toBound() - ) - else -> when (val ctor = constructor.declarationDescriptor) { - is TypeParameterDescriptor -> TypeParameter( - dri = DRI.from(ctor), - name = ctor.name.asString(), - presentableName = annotations.getPresentableName(), - extra = annotations() - ) - is FunctionClassDescriptor -> FunctionalTypeConstructor( - DRI.from(ctor), - arguments.map { it.toProjection() }, - isExtensionFunction = isExtensionFunctionType || isBuiltinExtensionFunctionalType, - isSuspendable = isSuspendFunctionTypeOrSubtype, - presentableName = annotations.getPresentableName(), - extra = annotations() - ) - else -> GenericTypeConstructor( - DRI.from(ctor!!), // TODO: remove '!!' - arguments.map { it.toProjection() }, - annotations.getPresentableName(), - extra = annotations() - ) - }.let { - if (isMarkedNullable) Nullable(it) else it - } - } - } - - private suspend fun TypeProjection.toProjection(): Projection = - if (isStarProjection) Star else formPossiblyVariant() - - private suspend fun TypeProjection.formPossiblyVariant(): Projection = - type.toBound().wrapWithVariance(projectionKind) - - private fun TypeParameterDescriptor.variantTypeParameter(type: TypeParameter) = - type.wrapWithVariance(variance) - - private fun <T : Bound> T.wrapWithVariance(variance: org.jetbrains.kotlin.types.Variance) = - when (variance) { - org.jetbrains.kotlin.types.Variance.INVARIANT -> Invariance(this) - org.jetbrains.kotlin.types.Variance.IN_VARIANCE -> Contravariance(this) - org.jetbrains.kotlin.types.Variance.OUT_VARIANCE -> Covariance(this) - } - - private fun descriptorToAnyDeclaration(descriptor: DeclarationDescriptor): PsiElement? { - val effectiveReferencedDescriptors = DescriptorToSourceUtils.getEffectiveReferencedDescriptors(descriptor) - //take any - return effectiveReferencedDescriptors.firstOrNull()?.let { DescriptorToSourceUtils.getSourceFromDescriptor(it) } - } - - private fun DeclarationDescriptor.getDocumentation() = (findKDoc(::descriptorToAnyDeclaration)?.let { - MarkdownParser.parseFromKDocTag( - kDocTag = it, - externalDri = { link: String -> - try { - resolveKDocLink( - context = resolutionFacade.resolveSession.bindingContext, - resolutionFacade = resolutionFacade, - fromDescriptor = this, - fromSubjectOfTag = null, - qualifiedName = link.split('.') - ).firstOrNull()?.let { DRI.from(it) } - } catch (e1: IllegalArgumentException) { - logger.warn("Couldn't resolve link for $link") - null - } - }, - kdocLocation = toSourceElement.containingFile.name?.let { - val fqName = fqNameOrNull()?.asString() - if (fqName != null) "$it/$fqName" - else it - } - ) - } ?: getJavaDocs())?.takeIf { it.children.isNotEmpty() } - - private fun DeclarationDescriptor.getJavaDocs(): DocumentationNode? { - val overriddenDescriptors = (this as? CallableDescriptor)?.overriddenDescriptors ?: emptyList() - val allDescriptors = overriddenDescriptors + listOf(this) - return allDescriptors - .mapNotNull { it.findPsi() as? PsiNamedElement } - .firstOrNull() - ?.let { javadocParser.parseDocumentation(it) } - } - - private suspend fun ClassDescriptor.companion(dri: DRIWithPlatformInfo): DObject? = companionObjectDescriptor?.let { - objectDescriptor(it, dri) - } - - private fun MemberDescriptor.modifier() = when (modality) { - Modality.FINAL -> KotlinModifier.Final - Modality.SEALED -> KotlinModifier.Sealed - Modality.OPEN -> KotlinModifier.Open - Modality.ABSTRACT -> KotlinModifier.Abstract - else -> KotlinModifier.Empty - } - - private fun MemberDescriptor.createSources(): SourceSetDependent<DocumentableSource> = - DescriptorDocumentableSource(this).toSourceSetDependent() - - private fun FunctionDescriptor.additionalExtras() = listOfNotNull( - ExtraModifiers.KotlinOnlyModifiers.Infix.takeIf { isInfix }, - ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline }, - ExtraModifiers.KotlinOnlyModifiers.Suspend.takeIf { isSuspend }, - ExtraModifiers.KotlinOnlyModifiers.Operator.takeIf { isOperator }, - ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() }, - ExtraModifiers.KotlinOnlyModifiers.TailRec.takeIf { isTailrec }, - ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal }, - ExtraModifiers.KotlinOnlyModifiers.Override.takeIf { DescriptorUtils.isOverride(this) } - ).toSet() - - private fun ClassDescriptor.additionalExtras() = listOfNotNull( - ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline }, - ExtraModifiers.KotlinOnlyModifiers.Value.takeIf { isValue }, - ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal }, - ExtraModifiers.KotlinOnlyModifiers.Inner.takeIf { isInner }, - ExtraModifiers.KotlinOnlyModifiers.Data.takeIf { isData }, - ExtraModifiers.KotlinOnlyModifiers.Fun.takeIf { isFun }, - ).toSet() - - private fun ValueParameterDescriptor.additionalExtras() = listOfNotNull( - ExtraModifiers.KotlinOnlyModifiers.NoInline.takeIf { isNoinline }, - ExtraModifiers.KotlinOnlyModifiers.CrossInline.takeIf { isCrossinline }, - ExtraModifiers.KotlinOnlyModifiers.Const.takeIf { isConst }, - ExtraModifiers.KotlinOnlyModifiers.LateInit.takeIf { isLateInit }, - ExtraModifiers.KotlinOnlyModifiers.VarArg.takeIf { isVararg } - ).toSet() - - private fun TypeParameterDescriptor.additionalExtras() = listOfNotNull( - ExtraModifiers.KotlinOnlyModifiers.Reified.takeIf { isReified } - ).toSet() - - private fun PropertyDescriptor.additionalExtras() = listOfNotNull( - ExtraModifiers.KotlinOnlyModifiers.Const.takeIf { isConst }, - ExtraModifiers.KotlinOnlyModifiers.LateInit.takeIf { isLateInit }, - ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() }, - ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal }, - ExtraModifiers.KotlinOnlyModifiers.Override.takeIf { DescriptorUtils.isOverride(this) } - ) - - private suspend fun Annotated.getAnnotations() = annotations.parallelMapNotNull { it.toAnnotation() } - - private fun ConstantValue<*>.toValue(): AnnotationParameterValue = when (this) { - is ConstantsAnnotationValue -> AnnotationValue(value.toAnnotation()) - is ConstantsArrayValue -> ArrayValue(value.map { it.toValue() }) - is ConstantsEnumValue -> EnumValue( - fullEnumEntryName(), - DRI(enumClassId.packageFqName.asString(), fullEnumEntryName()) - ) - is ConstantsKtClassValue -> when (value) { - is NormalClass -> (value as NormalClass).value.classId.let { - ClassValue( - it.relativeClassName.asString(), - DRI(it.packageFqName.asString(), it.relativeClassName.asString()) - ) - } - is LocalClass -> (value as LocalClass).type.let { - ClassValue( - it.toString(), - DRI.from(it.constructor.declarationDescriptor as DeclarationDescriptor) - ) - } - } - is ConstantsFloatValue -> FloatValue(value) - is ConstantsDoubleValue -> DoubleValue(value) - is ConstantsUIntValue -> IntValue(value) - is ConstantsULongValue -> LongValue(value) - is ConstantsIntValue -> IntValue(value) - is ConstantsLongValue -> LongValue(value) - is ConstantsBooleanValue -> BooleanValue(value) - is ConstantsNullValue -> NullValue - else -> StringValue(unquotedValue(toString())) - } - - private fun AnnotationDescriptor.toAnnotation(scope: Annotations.AnnotationScope = Annotations.AnnotationScope.DIRECT): Annotations.Annotation = - Annotations.Annotation( - DRI.from(annotationClass as DeclarationDescriptor), - allValueArguments.map { it.key.asString() to it.value.toValue() }.toMap(), - mustBeDocumented(), - scope - ) - - private fun AnnotationDescriptor.mustBeDocumented(): Boolean = - if (source.toString() == "NO_SOURCE") false - else annotationClass?.annotations?.hasAnnotation(FqName("kotlin.annotation.MustBeDocumented")) ?: false - - private suspend fun PropertyDescriptor.getAnnotationsWithBackingField(): List<Annotations.Annotation> = - getAnnotations() + (backingField?.getAnnotations() ?: emptyList()) - - private fun List<Annotations.Annotation>.toAdditionalExtras() = mapNotNull { - try { - ExtraModifiers.valueOf(it.dri.classNames?.toLowerCase() ?: "") - } catch (e: IllegalArgumentException) { - null - } - } - - private fun <T : CallableMemberDescriptor> T.getConcreteDescriptor(): T { - return if (kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { - this - } else { - @Suppress("UNCHECKED_CAST") - overriddenDescriptors.first().getConcreteDescriptor() as T - } - } - - private fun ValueParameterDescriptor.getDefaultValue(): Expression? = - ((source as? KotlinSourceElement)?.psi as? KtParameter)?.defaultValue?.toDefaultValueExpression() - - private fun PropertyDescriptor.getDefaultValue(): Expression? = - (source as? KotlinSourceElement)?.psi?.children?.filterIsInstance<KtConstantExpression>()?.firstOrNull() - ?.toDefaultValueExpression() - - private fun ClassDescriptor.getAppliedConstructorParameters() = - (source as PsiSourceElement).psi?.children?.flatMap { - it.safeAs<KtInitializerList>()?.initializersAsExpression().orEmpty() - }.orEmpty() - - private fun KtInitializerList.initializersAsExpression() = - initializers.firstIsInstanceOrNull<KtCallElement>() - ?.getValueArgumentsInParentheses() - ?.map { it.getArgumentExpression()?.toDefaultValueExpression() ?: ComplexExpression("") } - .orEmpty() - - private fun KtExpression.toDefaultValueExpression(): Expression? = when (node?.elementType) { - KtNodeTypes.INTEGER_CONSTANT -> parseLong(node?.text)?.let { IntegerConstant(it) } - KtNodeTypes.FLOAT_CONSTANT -> if (node?.text?.toLowerCase()?.endsWith('f') == true) - parseFloat(node?.text)?.let { FloatConstant(it) } - else parseDouble(node?.text)?.let { DoubleConstant(it) } - KtNodeTypes.BOOLEAN_CONSTANT -> BooleanConstant(node?.text == "true") - KtNodeTypes.STRING_TEMPLATE -> StringConstant(node.findChildByType(KtNodeTypes.LITERAL_STRING_TEMPLATE_ENTRY)?.text.orEmpty()) - else -> node?.text?.let { ComplexExpression(it) } - } - - private data class ClassInfo(val ancestry: AncestryNode, val docs: SourceSetDependent<DocumentationNode>) { - val supertypes: List<TypeConstructorWithKind> - get() = listOfNotNull(ancestry.superclass?.let { - it.typeConstructor.let { - TypeConstructorWithKind( - it, - KotlinClassKindTypes.CLASS - ) - } - }) + ancestry.interfaces.map { TypeConstructorWithKind(it.typeConstructor, KotlinClassKindTypes.INTERFACE) } - } - - private fun DescriptorVisibility.toDokkaVisibility(): Visibility = when (this.delegate) { - Visibilities.Public -> KotlinVisibility.Public - Visibilities.Protected -> KotlinVisibility.Protected - Visibilities.Internal -> KotlinVisibility.Internal - Visibilities.Private, Visibilities.PrivateToThis -> KotlinVisibility.Private - JavaVisibilities.ProtectedAndPackage -> KotlinVisibility.Protected - JavaVisibilities.ProtectedStaticVisibility -> KotlinVisibility.Protected - JavaVisibilities.PackageVisibility -> JavaVisibility.Default - else -> KotlinVisibility.Public - } - - private fun ConstantsEnumValue.fullEnumEntryName() = - "${this.enumClassId.relativeClassName.asString()}.${this.enumEntryName.identifier}" - - private fun DeclarationDescriptorWithSource.ktFile(): KtFile? = - (source.containingFile as? PsiSourceFile)?.psiFile as? KtFile - - private suspend fun DeclarationDescriptorWithSource.fileLevelAnnotations() = ktFile() - ?.let { file -> resolutionFacade.resolveSession.getFileAnnotations(file) } - ?.toList() - ?.parallelMap { it.toAnnotation(scope = Annotations.AnnotationScope.FILE) } - .orEmpty() - - private fun AncestryNode.exceptionInSupertypesOrNull(): ExceptionInSupertypes? = - typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() }?.let { ExceptionInSupertypes(it.toSourceSetDependent()) } -} - diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultExternalDocumentablesProvider.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultExternalDocumentablesProvider.kt deleted file mode 100644 index 05982301..00000000 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultExternalDocumentablesProvider.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.jetbrains.dokka.base.translators.descriptors - -import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.DClasslike -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.PackageViewDescriptor -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.resolve.scopes.MemberScope -import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered - -class DefaultExternalDocumentablesProvider(context: DokkaContext) : ExternalDocumentablesProvider { - private val analysis = context.plugin<DokkaBase>().querySingle { kotlinAnalysis } - - private val translator = context.plugin<DokkaBase>().querySingle { externalClasslikesTranslator } - - override fun findClasslike(dri: DRI, sourceSet: DokkaSourceSet): DClasslike? { - val pkg = dri.packageName?.let { FqName(it) } ?: FqName.ROOT - val names = dri.classNames?.split('.') ?: return null - - val packageDsc = analysis[sourceSet].facade.moduleDescriptor.getPackage(pkg) - val classDsc = names.fold<String, DeclarationDescriptor?>(packageDsc) { dsc, name -> - dsc?.scope?.getDescriptorsFiltered { it.identifier == name } - ?.filterIsInstance<ClassDescriptor>() - ?.firstOrNull() - } - - return (classDsc as? ClassDescriptor)?.let { translator.translateClassDescriptor(it, sourceSet) } - } - - private val DeclarationDescriptor.scope: MemberScope - get() = when (this) { - is PackageViewDescriptor -> memberScope - is ClassDescriptor -> unsubstitutedMemberScope - else -> throw IllegalArgumentException("Unexpected type of descriptor: ${this::class}") - } -}
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DescriptorAccessorConventionUtil.kt b/plugins/base/src/main/kotlin/translators/descriptors/DescriptorAccessorConventionUtil.kt deleted file mode 100644 index 292dbfca..00000000 --- a/plugins/base/src/main/kotlin/translators/descriptors/DescriptorAccessorConventionUtil.kt +++ /dev/null @@ -1,145 +0,0 @@ -package org.jetbrains.dokka.base.translators.descriptors - -import org.jetbrains.dokka.base.translators.firstNotNullOfOrNull -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.descriptors.PropertyDescriptor -import org.jetbrains.kotlin.load.java.JvmAbi -import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor -import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName -import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName - -internal data class DescriptorFunctionsHolder( - val regularFunctions: List<FunctionDescriptor>, - val accessors: Map<PropertyDescriptor, DescriptorAccessorHolder> -) - -internal data class DescriptorAccessorHolder( - val getter: FunctionDescriptor? = null, - val setter: FunctionDescriptor? = null -) - -/** - * Separate regular Kotlin/Java functions and inherited Java accessors - * to properly display properties inherited from Java. - * - * Take this example: - * ``` - * // java - * public class JavaClass { - * private int a = 1; - * public int getA() { return a; } - * public void setA(int a) { this.a = a; } - * } - * - * // kotlin - * class Bar : JavaClass() { - * fun foo() {} - * } - * ``` - * - * It should result in: - * - 1 regular function `foo` - * - Map a=[`getA`, `setA`] - */ -internal fun splitFunctionsAndInheritedAccessors( - properties: List<PropertyDescriptor>, - functions: List<FunctionDescriptor> -): DescriptorFunctionsHolder { - val (javaMethods, kotlinFunctions) = functions.partition { it is JavaMethodDescriptor } - if (javaMethods.isEmpty()) { - return DescriptorFunctionsHolder(regularFunctions = kotlinFunctions, emptyMap()) - } - - val propertiesByName = properties.associateBy { it.name.asString() } - val regularFunctions = ArrayList<FunctionDescriptor>(kotlinFunctions) - - val accessors = mutableMapOf<PropertyDescriptor, DescriptorAccessorHolder>() - javaMethods.forEach { function -> - val possiblePropertyNamesForFunction = function.toPossiblePropertyNames() - val property = possiblePropertyNamesForFunction.firstNotNullOfOrNull { propertiesByName[it] } - if (property != null && function.isAccessorFor(property)) { - accessors.compute(property) { prop, accessorHolder -> - if (function.isGetterFor(prop)) - accessorHolder?.copy(getter = function) ?: DescriptorAccessorHolder(getter = function) - else - accessorHolder?.copy(setter = function) ?: DescriptorAccessorHolder(setter = function) - } - } else { - regularFunctions.add(function) - } - } - - val accessorLookalikes = removeNonAccessorsReturning(accessors) - regularFunctions.addAll(accessorLookalikes) - - return DescriptorFunctionsHolder(regularFunctions, accessors) -} - -/** - * If a field has no getter, it's not accessible as a property from Kotlin's perspective, - * but it still might have a setter lookalike. In this case, this "setter" should be just a regular function - * - * @return removed elements - */ -private fun removeNonAccessorsReturning( - propertyAccessors: MutableMap<PropertyDescriptor, DescriptorAccessorHolder> -): List<FunctionDescriptor> { - val nonAccessors = mutableListOf<FunctionDescriptor>() - propertyAccessors.entries.removeIf { (_, accessors) -> - if (accessors.getter == null && accessors.setter != null) { - nonAccessors.add(accessors.setter) - true - } else { - false - } - } - return nonAccessors -} - -private fun FunctionDescriptor.toPossiblePropertyNames(): List<String> { - val stringName = this.name.asString() - return when { - JvmAbi.isSetterName(stringName) -> propertyNamesBySetMethodName(this.name).map { it.asString() } - JvmAbi.isGetterName(stringName) -> propertyNamesByGetMethod(this) - else -> listOf() - } -} - -private fun propertyNamesByGetMethod(functionDescriptor: FunctionDescriptor): List<String> { - val stringName = functionDescriptor.name.asString() - // In java, the convention for boolean property accessors is as follows: - // - `private boolean active;` - // - `private boolean isActive();` - // - // Whereas in Kotlin, because there are no explicit accessors, the convention is - // - `val isActive: Boolean` - // - // This makes it difficult to guess the name of the accessor property in case of Java - val javaPropName = if (functionDescriptor is JavaMethodDescriptor && JvmAbi.startsWithIsPrefix(stringName)) { - val javaPropName = stringName.removePrefix("is").let { newName -> - newName.replaceFirst(newName[0], newName[0].toLowerCase()) - } - javaPropName - } else { - null - } - val kotlinPropName = propertyNameByGetMethodName(functionDescriptor.name)?.asString() - return listOfNotNull(javaPropName, kotlinPropName) -} - -private fun FunctionDescriptor.isAccessorFor(property: PropertyDescriptor): Boolean { - return (this.isGetterFor(property) || this.isSetterFor(property)) - && !property.visibility.isPublicAPI - && this.visibility.isPublicAPI -} - -private fun FunctionDescriptor.isGetterFor(property: PropertyDescriptor): Boolean { - return this.returnType == property.returnType - && this.valueParameters.isEmpty() -} - -private fun FunctionDescriptor.isSetterFor(property: PropertyDescriptor): Boolean { - return this.valueParameters.size == 1 - && this.valueParameters[0].type == property.returnType -} - diff --git a/plugins/base/src/main/kotlin/translators/descriptors/ExternalClasslikesTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/ExternalClasslikesTranslator.kt deleted file mode 100644 index a5385c46..00000000 --- a/plugins/base/src/main/kotlin/translators/descriptors/ExternalClasslikesTranslator.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.jetbrains.dokka.base.translators.descriptors - -import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.model.DClasslike -import org.jetbrains.kotlin.descriptors.ClassDescriptor - -/** - * Service translating [ClassDescriptor]s of symbols defined outside of documented project to [DClasslike]s. - */ -fun interface ExternalClasslikesTranslator { - fun translateClassDescriptor(descriptor: ClassDescriptor, sourceSet: DokkaSourceSet): DClasslike -}
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/descriptors/ExternalDocumentablesProvider.kt b/plugins/base/src/main/kotlin/translators/descriptors/ExternalDocumentablesProvider.kt deleted file mode 100644 index e6d499f4..00000000 --- a/plugins/base/src/main/kotlin/translators/descriptors/ExternalDocumentablesProvider.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.jetbrains.dokka.base.translators.descriptors - -import org.jetbrains.dokka.DokkaConfiguration -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.DClasslike - -/** - * Service that can be queried with [DRI] and source set to obtain a documentable for classlike. - * - * There are some cases when there is a need to process documentables of classlikes that were not defined - * in the project itself but are somehow related to the symbols defined in the documented project (e.g. are supertypes - * of classes defined in project). - */ -fun interface ExternalDocumentablesProvider { - - /** - * Returns [DClasslike] matching provided [DRI] in specified source set. - * - * Result is null if compiler haven't generated matching class descriptor. - */ - fun findClasslike(dri: DRI, sourceSet: DokkaConfiguration.DokkaSourceSet): DClasslike? -}
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/descriptors/SyntheticDescriptorDocumentationProvider.kt b/plugins/base/src/main/kotlin/translators/descriptors/SyntheticDescriptorDocumentationProvider.kt deleted file mode 100644 index c96b888a..00000000 --- a/plugins/base/src/main/kotlin/translators/descriptors/SyntheticDescriptorDocumentationProvider.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.jetbrains.dokka.base.translators.descriptors - -import org.jetbrains.dokka.analysis.DokkaResolutionFacade -import org.jetbrains.dokka.analysis.from -import org.jetbrains.dokka.base.parsers.MarkdownParser -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.doc.DocumentationNode -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink -import org.jetbrains.kotlin.resolve.DescriptorFactory - -private const val ENUM_VALUEOF_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumValueOf.kt.template" -private const val ENUM_VALUES_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumValues.kt.template" - - internal class SyntheticDescriptorDocumentationProvider( - private val resolutionFacade: DokkaResolutionFacade -) { - fun isDocumented(descriptor: DeclarationDescriptor): Boolean = descriptor is FunctionDescriptor - && (DescriptorFactory.isEnumValuesMethod(descriptor) || DescriptorFactory.isEnumValueOfMethod(descriptor)) - - fun getDocumentation(descriptor: DeclarationDescriptor): DocumentationNode? { - val function = descriptor as? FunctionDescriptor ?: return null - return when { - DescriptorFactory.isEnumValuesMethod(function) -> loadTemplate(descriptor, ENUM_VALUES_TEMPLATE_PATH) - DescriptorFactory.isEnumValueOfMethod(function) -> loadTemplate(descriptor, ENUM_VALUEOF_TEMPLATE_PATH) - else -> null - } - } - - private fun loadTemplate(descriptor: DeclarationDescriptor, filePath: String): DocumentationNode? { - val kdoc = loadContent(filePath) ?: return null - val parser = MarkdownParser({ link -> resolveLink(descriptor, link)}, filePath) - return parser.parse(kdoc) - } - - private fun loadContent(filePath: String): String? = javaClass.getResource(filePath)?.readText() - - private fun resolveLink(descriptor: DeclarationDescriptor, link: String): DRI? = - resolveKDocLink( - resolutionFacade.resolveSession.bindingContext, - resolutionFacade, - descriptor, - null, - link.split('.') - ).firstOrNull()?.let { DRI.from(it) } -} diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt index a385e0e4..d8582ec5 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt @@ -6,6 +6,7 @@ import org.jetbrains.dokka.model.DModule import org.jetbrains.dokka.pages.ModulePageNode import org.jetbrains.dokka.plugability.* import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator +import org.jetbrains.kotlin.analysis.kotlin.internal.InternalKotlinAnalysisPlugin class DefaultDocumentableToPageTranslator( context: DokkaContext @@ -14,6 +15,7 @@ class DefaultDocumentableToPageTranslator( private val commentsToContentConverter = context.plugin<DokkaBase>().querySingle { commentsToContentConverter } private val signatureProvider = context.plugin<DokkaBase>().querySingle { signatureProvider } private val customTagContentProviders = context.plugin<DokkaBase>().query { customTagContentProvider } + private val documentableSourceLanguageParser = context.plugin<InternalKotlinAnalysisPlugin>().querySingle { documentableSourceLanguageParser } private val logger = context.logger override fun invoke(module: DModule): ModulePageNode = @@ -22,6 +24,7 @@ class DefaultDocumentableToPageTranslator( commentsToContentConverter, signatureProvider, logger, - customTagContentProviders + customTagContentProviders, + documentableSourceLanguageParser ).pageForModule(module) -}
\ No newline at end of file +} diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index ffc7fd85..8df785c0 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -18,7 +18,8 @@ import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.utilities.DokkaLogger -import java.util.Comparator +import org.jetbrains.kotlin.analysis.kotlin.internal.DocumentableSourceLanguageParser +import org.jetbrains.kotlin.analysis.kotlin.internal.DocumentableLanguage import kotlin.reflect.KClass internal typealias GroupedTags = Map<KClass<out TagWrapper>, List<Pair<DokkaSourceSet?, TagWrapper>>> @@ -28,7 +29,8 @@ open class DefaultPageCreator( commentsToContentConverter: CommentsToContentConverter, signatureProvider: SignatureProvider, val logger: DokkaLogger, - val customTagContentProviders: List<CustomTagContentProvider> = emptyList() + val customTagContentProviders: List<CustomTagContentProvider> = emptyList(), + val documentableAnalyzer: DocumentableSourceLanguageParser ) { protected open val contentBuilder = PageContentBuilder(commentsToContentConverter, signatureProvider, logger) @@ -506,12 +508,12 @@ open class DefaultPageCreator( sourceSet: DokkaSourceSet, tag: TagWrapper ) { - (documentable as? WithSources)?.documentableLanguage(sourceSet)?.let { - when (it) { - DocumentableLanguage.KOTLIN -> firstParagraphComment(tag.root) - DocumentableLanguage.JAVA -> firstSentenceComment(tag.root) - } - } ?: firstParagraphComment(tag.root) + val language = documentableAnalyzer.getLanguage(documentable, sourceSet) + when(language) { + DocumentableLanguage.JAVA -> firstSentenceComment(tag.root) + DocumentableLanguage.KOTLIN -> firstParagraphComment(tag.root) + else -> firstParagraphComment(tag.root) + } } protected open fun contentForFunction(f: DFunction) = contentForMember(f) diff --git a/plugins/base/src/main/kotlin/translators/documentables/DeprecationSectionCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DeprecationSectionCreator.kt index 73c36d8d..7e5926a0 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DeprecationSectionCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DeprecationSectionCreator.kt @@ -3,11 +3,11 @@ package org.jetbrains.dokka.base.translators.documentables import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.annotations import org.jetbrains.dokka.base.transformers.documentables.isDeprecated +import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder import org.jetbrains.dokka.model.* import org.jetbrains.dokka.pages.ContentKind import org.jetbrains.dokka.pages.ContentStyle import org.jetbrains.dokka.pages.TextStyle -import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder /** * Main header for [Deprecated] section diff --git a/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt b/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt index 35645a73..6c5d885d 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt @@ -16,7 +16,6 @@ import org.jetbrains.dokka.pages.ContentKind import org.jetbrains.dokka.pages.ContentStyle import org.jetbrains.dokka.pages.TextStyle import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.kotlin.utils.addToStdlib.safeAs import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf @@ -250,10 +249,15 @@ internal fun PageContentBuilder.DocumentableContentBuilder.inheritorsSectionCont multiplatformInheritorsSectionContent(documentable, inheritors, logger) } -private fun WithScope.inheritors() = safeAs<WithExtraProperties<Documentable>>() - ?.let { it.extra[InheritorsInfo] } - ?.let { inheritors -> inheritors.value.filter { it.value.isNotEmpty() } } - .orEmpty() +private fun WithScope.inheritors(): SourceSetDependent<List<DRI>> { + @Suppress("UNCHECKED_CAST") + val withExtra = this as? WithExtraProperties<Documentable> + + return withExtra + ?.let { it.extra[InheritorsInfo] } + ?.let { inheritors -> inheritors.value.filter { it.value.isNotEmpty() } } + .orEmpty() +} /** * Detect that documentable is located only in the shared code without expect-actuals diff --git a/plugins/base/src/main/kotlin/translators/documentables/briefFromContentNodes.kt b/plugins/base/src/main/kotlin/translators/documentables/briefFromContentNodes.kt index bd54eedd..092077d6 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/briefFromContentNodes.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/briefFromContentNodes.kt @@ -1,15 +1,17 @@ package org.jetbrains.dokka.base.translators.documentables -import org.jetbrains.dokka.model.doc.* +import org.jetbrains.dokka.base.utils.firstNotNullOfOrNull +import org.jetbrains.dokka.model.doc.CustomDocTag +import org.jetbrains.dokka.model.doc.DocTag +import org.jetbrains.dokka.model.doc.P +import org.jetbrains.dokka.model.doc.Text import org.jetbrains.dokka.model.withDescendants import org.jetbrains.dokka.pages.* -import org.jetbrains.kotlin.util.firstNotNullResult -import org.jetbrains.kotlin.utils.addToStdlib.safeAs fun firstParagraphBrief(docTag: DocTag): DocTag? = when(docTag){ is P -> docTag - is CustomDocTag -> docTag.children.firstNotNullResult { firstParagraphBrief(it) } + is CustomDocTag -> docTag.children.firstNotNullOfOrNull { firstParagraphBrief(it) } is Text -> docTag else -> null } @@ -18,7 +20,7 @@ fun firstSentenceBriefFromContentNodes(description: List<ContentNode>): List<Con val firstSentenceRegex = """^((?:[^.?!]|[.!?](?!\s))*[.!?])""".toRegex() //Description that is entirely based on html content. In html it is hard to define a brief so we render all of it - if(description.all { it.withDescendants().all { it is ContentGroup || it.safeAs<ContentText>()?.isHtml == true } }){ + if(description.all { it.withDescendants().all { it is ContentGroup || (it as? ContentText)?.isHtml == true } }){ return description } diff --git a/plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt b/plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt deleted file mode 100644 index b3ce7c5c..00000000 --- a/plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.jetbrains.dokka.base.translators.documentables - -import org.jetbrains.dokka.DokkaConfiguration -import org.jetbrains.dokka.analysis.PsiDocumentableSource -import org.jetbrains.dokka.model.WithSources - -internal enum class DocumentableLanguage { - JAVA, KOTLIN -} - -internal fun WithSources.documentableLanguage(sourceSet: DokkaConfiguration.DokkaSourceSet): DocumentableLanguage = - when (sources[sourceSet]) { - is PsiDocumentableSource -> DocumentableLanguage.JAVA - else -> DocumentableLanguage.KOTLIN - }
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/isException.kt b/plugins/base/src/main/kotlin/translators/isException.kt deleted file mode 100644 index d148cd34..00000000 --- a/plugins/base/src/main/kotlin/translators/isException.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.jetbrains.dokka.base.translators - -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.AncestryNode -import org.jetbrains.dokka.model.TypeConstructor - -internal fun AncestryNode.typeConstructorsBeingExceptions(): List<TypeConstructor> { - fun traverseSupertypes(ancestry: AncestryNode): List<TypeConstructor> = - listOf(ancestry.typeConstructor) + (ancestry.superclass?.let(::traverseSupertypes) ?: emptyList()) - - return superclass?.let(::traverseSupertypes)?.filter { type -> type.dri.isDirectlyAnException() } ?: emptyList() -} - -internal fun DRI.isDirectlyAnException(): Boolean = - toString().let { stringed -> - stringed == "kotlin/Exception///PointingToDeclaration/" || - stringed == "java.lang/Exception///PointingToDeclaration/" - } diff --git a/plugins/base/src/main/kotlin/translators/parseWithNormalisedSpaces.kt b/plugins/base/src/main/kotlin/translators/parseWithNormalisedSpaces.kt deleted file mode 100644 index 7bda9d0b..00000000 --- a/plugins/base/src/main/kotlin/translators/parseWithNormalisedSpaces.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.jetbrains.dokka.base.translators - -import org.intellij.markdown.lexer.Compat.codePointToString -import org.intellij.markdown.lexer.Compat.forEachCodePoint -import org.jetbrains.dokka.model.doc.DocTag -import org.jetbrains.dokka.model.doc.DocTag.Companion.contentTypeParam -import org.jetbrains.dokka.model.doc.Text -import org.jsoup.Jsoup -import org.jsoup.internal.StringUtil -import org.jsoup.nodes.Entities - -internal fun String.parseHtmlEncodedWithNormalisedSpaces( - renderWhiteCharactersAsSpaces: Boolean -): List<DocTag> { - val accum = StringBuilder() - val tags = mutableListOf<DocTag>() - var lastWasWhite = false - - forEachCodePoint { c -> - if (renderWhiteCharactersAsSpaces && StringUtil.isWhitespace(c)) { - if (!lastWasWhite) { - accum.append(' ') - lastWasWhite = true - } - } else if (codePointToString(c).let { it != Entities.escape(it) }) { - accum.toString().takeIf { it.isNotBlank() }?.let { tags.add(Text(it)) } - accum.delete(0, accum.length) - - accum.appendCodePoint(c) - tags.add(Text(accum.toString(), params = contentTypeParam("html"))) - accum.delete(0, accum.length) - } else if (!StringUtil.isInvisibleChar(c)) { - accum.appendCodePoint(c) - lastWasWhite = false - } - } - accum.toString().takeIf { it.isNotBlank() }?.let { tags.add(Text(it)) } - return tags -} - -/** - * Parses string into [Text] doc tags that can have either value of the string or html-encoded value with content-type=html parameter. - * Content type is added when dealing with html entries like ` ` - */ -internal fun String.parseWithNormalisedSpaces( - renderWhiteCharactersAsSpaces: Boolean -): List<DocTag> { - if (!requiresHtmlEncoding()) { - return parseHtmlEncodedWithNormalisedSpaces(renderWhiteCharactersAsSpaces) - } - // parsing it using jsoup is required to get codePoints, otherwise they are interpreted separately, as chars - // But we dont need to do it for java as it is already parsed with jsoup - return Jsoup.parseBodyFragment(this).body().wholeText().parseHtmlEncodedWithNormalisedSpaces(renderWhiteCharactersAsSpaces) -} - -private fun String.requiresHtmlEncoding(): Boolean = indexOf('&') != -1 diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt deleted file mode 100644 index c410104c..00000000 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ /dev/null @@ -1,863 +0,0 @@ -package org.jetbrains.dokka.base.translators.psi - -import com.intellij.lang.jvm.JvmModifier -import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute -import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue -import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue -import com.intellij.lang.jvm.annotation.JvmAnnotationEnumFieldValue -import com.intellij.lang.jvm.types.JvmReferenceType -import com.intellij.openapi.vfs.VirtualFileManager -import com.intellij.psi.* -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope -import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.analysis.DokkaResolutionFacade -import org.jetbrains.dokka.analysis.KotlinAnalysis -import org.jetbrains.dokka.analysis.PsiDocumentableSource -import org.jetbrains.dokka.analysis.from -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser -import org.jetbrains.dokka.base.translators.typeConstructorsBeingExceptions -import org.jetbrains.dokka.base.translators.unquotedValue -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.links.nextTarget -import org.jetbrains.dokka.links.withClass -import org.jetbrains.dokka.links.withEnumEntryExtra -import org.jetbrains.dokka.model.* -import org.jetbrains.dokka.model.AnnotationTarget -import org.jetbrains.dokka.model.doc.DocumentationNode -import org.jetbrains.dokka.model.doc.Param -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.dokka.transformers.sources.AsyncSourceToDocumentableTranslator -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.dokka.utilities.parallelForEach -import org.jetbrains.dokka.utilities.parallelMap -import org.jetbrains.dokka.utilities.parallelMapNotNull -import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation -import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys -import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot -import org.jetbrains.kotlin.idea.base.utils.fqname.getKotlinFqName -import org.jetbrains.kotlin.psi.psiUtil.getChildOfType -import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments -import org.jetbrains.kotlin.utils.addToStdlib.safeAs -import java.io.File - -class DefaultPsiToDocumentableTranslator( - context: DokkaContext, -) : AsyncSourceToDocumentableTranslator { - - private val kotlinAnalysis: KotlinAnalysis = context.plugin<DokkaBase>().querySingle { kotlinAnalysis } - - override suspend fun invokeSuspending(sourceSet: DokkaSourceSet, context: DokkaContext): DModule { - return coroutineScope { - fun isFileInSourceRoots(file: File): Boolean = - sourceSet.sourceRoots.any { root -> file.startsWith(root) } - - - val (environment, facade) = kotlinAnalysis[sourceSet] - - val sourceRoots = environment.configuration.get(CLIConfigurationKeys.CONTENT_ROOTS) - ?.filterIsInstance<JavaSourceRoot>() - ?.mapNotNull { it.file.takeIf(::isFileInSourceRoots) } - ?: listOf() - val localFileSystem = VirtualFileManager.getInstance().getFileSystem("file") - - val psiFiles = sourceRoots.parallelMap { sourceRoot -> - sourceRoot.absoluteFile.walkTopDown().mapNotNull { - localFileSystem.findFileByPath(it.path)?.let { vFile -> - PsiManager.getInstance(environment.project).findFile(vFile) as? PsiJavaFile - } - }.toList() - }.flatten() - - val docParser = - DokkaPsiParser( - sourceSet, - facade, - context.logger - ) - - DModule( - name = context.configuration.moduleName, - packages = psiFiles.parallelMapNotNull { it.safeAs<PsiJavaFile>() }.groupBy { it.packageName }.toList() - .parallelMap { (packageName: String, psiFiles: List<PsiJavaFile>) -> - docParser.parsePackage(packageName, psiFiles) - }, - documentation = emptyMap(), - expectPresentInSet = null, - sourceSets = setOf(sourceSet) - ) - } - } - - class DokkaPsiParser( - private val sourceSetData: DokkaSourceSet, - private val facade: DokkaResolutionFacade, - private val logger: DokkaLogger, - ) { - private val javadocParser = JavadocParser(logger, facade) - private val syntheticDocProvider = SyntheticElementDocumentationProvider(javadocParser, facade) - - private val cachedBounds = hashMapOf<String, Bound>() - - private val PsiMethod.hash: Int - get() = "$returnType $name$parameterList".hashCode() - - private val PsiField.hash: Int - get() = "$type $name".hashCode() - - private val PsiClassType.shouldBeIgnored: Boolean - get() = isClass("java.lang.Enum") || isClass("java.lang.Object") - - private fun PsiClassType.isClass(qName: String): Boolean { - val shortName = qName.substringAfterLast('.') - if (className == shortName) { - val psiClass = resolve() - return psiClass?.qualifiedName == qName - } - return false - } - - private fun <T> T.toSourceSetDependent() = mapOf(sourceSetData to this) - - suspend fun parsePackage(packageName: String, psiFiles: List<PsiJavaFile>): DPackage = coroutineScope { - val dri = DRI(packageName = packageName) - val packageInfo = psiFiles.singleOrNull { it.name == "package-info.java" } - val documentation = packageInfo?.let { - javadocParser.parseDocumentation(it).toSourceSetDependent() - }.orEmpty() - val annotations = packageInfo?.packageStatement?.annotationList?.annotations - - DPackage( - dri = dri, - functions = emptyList(), - properties = emptyList(), - classlikes = psiFiles.parallelMap { psiFile -> - coroutineScope { - psiFile.classes.asIterable().parallelMap { parseClasslike(it, dri) } - } - }.flatten(), - typealiases = emptyList(), - documentation = documentation, - expectPresentInSet = null, - sourceSets = setOf(sourceSetData), - extra = PropertyContainer.withAll( - annotations?.toList().orEmpty().toListOfAnnotations().toSourceSetDependent().toAnnotations() - ) - ) - } - - private suspend fun parseClasslike(psi: PsiClass, parent: DRI): DClasslike = coroutineScope { - with(psi) { - val dri = parent.withClass(name.toString()) - val superMethodsKeys = hashSetOf<Int>() - val superMethods = mutableListOf<Pair<PsiMethod, DRI>>() - val superFieldsKeys = hashSetOf<Int>() - val superFields = mutableListOf<Pair<PsiField, DRI>>() - methods.asIterable().parallelForEach { superMethodsKeys.add(it.hash) } - - /** - * Caution! This method mutates - * - superMethodsKeys - * - superMethods - * - superFieldsKeys - * - superKeys - */ - fun Array<PsiClassType>.getSuperTypesPsiClasses(): List<Pair<PsiClass, JavaClassKindTypes>> { - forEach { type -> - (type as? PsiClassType)?.resolve()?.let { - val definedAt = DRI.from(it) - it.methods.forEach { method -> - val hash = method.hash - if (!method.isConstructor && !superMethodsKeys.contains(hash) && - method.getVisibility() != JavaVisibility.Private - ) { - superMethodsKeys.add(hash) - superMethods.add(Pair(method, definedAt)) - } - } - it.fields.forEach { field -> - val hash = field.hash - if (!superFieldsKeys.contains(hash)) { - superFieldsKeys.add(hash) - superFields.add(Pair(field, definedAt)) - } - } - } - } - return filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi -> - supertypePsi.resolve()?.let { supertypePsiClass -> - val javaClassKind = when { - supertypePsiClass.isInterface -> JavaClassKindTypes.INTERFACE - else -> JavaClassKindTypes.CLASS - } - supertypePsiClass to javaClassKind - } - } - } - - fun traversePsiClassForAncestorsAndInheritedMembers(psiClass: PsiClass): AncestryNode { - val (classes, interfaces) = psiClass.superTypes.getSuperTypesPsiClasses() - .partition { it.second == JavaClassKindTypes.CLASS } - - return AncestryNode( - typeConstructor = GenericTypeConstructor( - DRI.from(psiClass), - psiClass.typeParameters.map { typeParameter -> - TypeParameter( - dri = DRI.from(typeParameter), - name = typeParameter.name.orEmpty(), - extra = typeParameter.annotations() - ) - } - ), - superclass = classes.singleOrNull()?.first?.let(::traversePsiClassForAncestorsAndInheritedMembers), - interfaces = interfaces.map { traversePsiClassForAncestorsAndInheritedMembers(it.first) } - ) - } - - val ancestry: AncestryNode = traversePsiClassForAncestorsAndInheritedMembers(this) - - val (regularFunctions, accessors) = splitFunctionsAndAccessors(psi.fields, psi.methods) - val (regularSuperFunctions, superAccessors) = splitFunctionsAndAccessors( - fields = superFields.map { it.first }.toTypedArray(), - methods = superMethods.map { it.first }.toTypedArray() - ) - - val regularSuperFunctionsKeys = regularSuperFunctions.map { it.hash }.toSet() - val regularSuperFunctionsWithDRI = superMethods.filter { it.first.hash in regularSuperFunctionsKeys } - - val superAccessorsWithDRI = superAccessors.mapValues { (field, methods) -> - val containsJvmField = field.annotations.mapNotNull { it.toAnnotation() }.any { it.isJvmField() } - if (containsJvmField) { - emptyList() - } else { - methods.mapNotNull { method -> superMethods.find { it.first.hash == method.hash } } - } - } - - val overridden = regularFunctions.flatMap { it.findSuperMethods().toList() } - val documentation = javadocParser.parseDocumentation(this).toSourceSetDependent() - val allFunctions = async { - val parsedRegularFunctions = regularFunctions.parallelMapNotNull { - if (!it.isConstructor) parseFunction( - it, - parentDRI = dri - ) else null - } - val parsedSuperFunctions = regularSuperFunctionsWithDRI - .filter { it.first !in overridden } - .parallelMap { parseFunction(it.first, inheritedFrom = it.second) } - - parsedRegularFunctions + parsedSuperFunctions - } - val allFields = async { - val parsedFields = fields.toList().parallelMapNotNull { - parseField(it, accessors[it].orEmpty()) - } - val parsedSuperFields = superFields.parallelMapNotNull { (field, dri) -> - parseFieldWithInheritingAccessors( - field, - superAccessorsWithDRI[field].orEmpty(), - inheritedFrom = dri - ) - } - parsedFields + parsedSuperFields - } - val source = parseSources() - val classlikes = async { innerClasses.asIterable().parallelMap { parseClasslike(it, dri) } } - val visibility = getVisibility().toSourceSetDependent() - val ancestors = (listOfNotNull(ancestry.superclass?.let { - it.typeConstructor.let { typeConstructor -> - TypeConstructorWithKind( - typeConstructor, - JavaClassKindTypes.CLASS - ) - } - }) + ancestry.interfaces.map { - TypeConstructorWithKind( - it.typeConstructor, - JavaClassKindTypes.INTERFACE - ) - }).toSourceSetDependent() - val modifiers = getModifier().toSourceSetDependent() - val implementedInterfacesExtra = - ImplementedInterfaces(ancestry.allImplementedInterfaces().toSourceSetDependent()) - - when { - isAnnotationType -> - DAnnotation( - name = name.orEmpty(), - dri = dri, - documentation = documentation, - expectPresentInSet = null, - sources = source, - functions = allFunctions.await(), - properties = allFields.await(), - classlikes = classlikes.await(), - visibility = visibility, - companion = null, - constructors = parseConstructors(dri), - generics = mapTypeParameters(dri), - sourceSets = setOf(sourceSetData), - isExpectActual = false, - extra = PropertyContainer.withAll( - implementedInterfacesExtra, - annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() - ) - ) - - isEnum -> DEnum( - dri = dri, - name = name.orEmpty(), - entries = fields.filterIsInstance<PsiEnumConstant>().map { entry -> - DEnumEntry( - dri = dri.withClass(entry.name).withEnumEntryExtra(), - name = entry.name, - documentation = javadocParser.parseDocumentation(entry).toSourceSetDependent(), - expectPresentInSet = null, - functions = emptyList(), - properties = emptyList(), - classlikes = emptyList(), - sourceSets = setOf(sourceSetData), - extra = PropertyContainer.withAll( - implementedInterfacesExtra, - annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() - ) - ) - }, - documentation = documentation, - expectPresentInSet = null, - sources = source, - functions = allFunctions.await(), - properties = fields.filter { it !is PsiEnumConstant } - .map { parseField(it, accessors[it].orEmpty()) }, - classlikes = classlikes.await(), - visibility = visibility, - companion = null, - constructors = parseConstructors(dri), - supertypes = ancestors, - sourceSets = setOf(sourceSetData), - isExpectActual = false, - extra = PropertyContainer.withAll( - implementedInterfacesExtra, - annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() - ) - ) - - isInterface -> DInterface( - dri = dri, - name = name.orEmpty(), - documentation = documentation, - expectPresentInSet = null, - sources = source, - functions = allFunctions.await(), - properties = allFields.await(), - classlikes = classlikes.await(), - visibility = visibility, - companion = null, - generics = mapTypeParameters(dri), - supertypes = ancestors, - sourceSets = setOf(sourceSetData), - isExpectActual = false, - extra = PropertyContainer.withAll( - implementedInterfacesExtra, - annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() - ) - ) - - else -> DClass( - dri = dri, - name = name.orEmpty(), - constructors = parseConstructors(dri), - functions = allFunctions.await(), - properties = allFields.await(), - classlikes = classlikes.await(), - sources = source, - visibility = visibility, - companion = null, - generics = mapTypeParameters(dri), - supertypes = ancestors, - documentation = documentation, - expectPresentInSet = null, - modifier = modifiers, - sourceSets = setOf(sourceSetData), - isExpectActual = false, - extra = PropertyContainer.withAll( - implementedInterfacesExtra, - annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations(), - ancestry.exceptionInSupertypesOrNull() - ) - ) - } - } - } - - /* - * Parameter `parentDRI` required for substitute package name: - * in the case of synthetic constructor, it will return empty from [DRI.Companion.from]. - */ - private fun PsiClass.parseConstructors(parentDRI: DRI): List<DFunction> { - val constructors = when { - isAnnotationType || isInterface -> emptyArray() - isEnum -> this.constructors - else -> this.constructors.takeIf { it.isNotEmpty() } ?: arrayOf(createDefaultConstructor()) - } - return constructors.map { parseFunction(psi = it, isConstructor = true, parentDRI = parentDRI) } - } - - /** - * PSI doesn't return a default constructor if class doesn't contain an explicit one. - * This method create synthetic constructor - * Visibility modifier is preserved from the class. - */ - private fun PsiClass.createDefaultConstructor(): PsiMethod { - val psiElementFactory = JavaPsiFacade.getElementFactory(facade.project) - val signature = when (val classVisibility = getVisibility()) { - JavaVisibility.Default -> name.orEmpty() - else -> "${classVisibility.name} $name" - } - return psiElementFactory.createConstructor(signature, this) - } - - private fun AncestryNode.exceptionInSupertypesOrNull(): ExceptionInSupertypes? = - typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() } - ?.let { ExceptionInSupertypes(it.toSourceSetDependent()) } - - private fun parseFunction( - psi: PsiMethod, - isConstructor: Boolean = false, - inheritedFrom: DRI? = null, - parentDRI: DRI? = null, - ): DFunction { - val dri = parentDRI?.let { dri -> - DRI.from(psi).copy(packageName = dri.packageName, classNames = dri.classNames) - } ?: DRI.from(psi) - val docs = psi.getDocumentation() - return DFunction( - dri = dri, - name = psi.name, - isConstructor = isConstructor, - parameters = psi.parameterList.parameters.map { psiParameter -> - DParameter( - dri = dri.copy(target = dri.target.nextTarget()), - name = psiParameter.name, - documentation = DocumentationNode( - listOfNotNull(docs.firstChildOfTypeOrNull<Param> { - it.name == psiParameter.name - }) - ).toSourceSetDependent(), - expectPresentInSet = null, - type = getBound(psiParameter.type), - sourceSets = setOf(sourceSetData), - extra = PropertyContainer.withAll( - psiParameter.annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() - ) - ) - }, - documentation = docs.toSourceSetDependent(), - expectPresentInSet = null, - sources = psi.parseSources(), - visibility = psi.getVisibility().toSourceSetDependent(), - type = psi.returnType?.let { getBound(type = it) } ?: Void, - generics = psi.mapTypeParameters(dri), - receiver = null, - modifier = psi.getModifier().toSourceSetDependent(), - sourceSets = setOf(sourceSetData), - isExpectActual = false, - extra = psi.additionalExtras().let { - PropertyContainer.withAll( - inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) }, - it.toSourceSetDependent().toAdditionalModifiers(), - (psi.annotations.toList() - .toListOfAnnotations() + it.toListOfAnnotations()).toSourceSetDependent() - .toAnnotations(), - ObviousMember.takeIf { psi.isObvious(inheritedFrom) }, - psi.throwsList.toDriList().takeIf { it.isNotEmpty() } - ?.let { CheckedExceptions(it.toSourceSetDependent()) } - ) - } - ) - } - - private fun PsiNamedElement.parseSources(): SourceSetDependent<DocumentableSource> { - return when { - // `isPhysical` detects the virtual declarations without real sources. - // Otherwise, `PsiDocumentableSource` initialization will fail: non-physical declarations doesn't have `virtualFile`. - // This check protects from accidentally requesting sources for synthetic / virtual declarations. - isPhysical -> PsiDocumentableSource(this).toSourceSetDependent() - else -> emptyMap() - } - } - - private fun PsiMethod.getDocumentation(): DocumentationNode = - this.takeIf { it is SyntheticElement }?.let { syntheticDocProvider.getDocumentation(it) } - ?: javadocParser.parseDocumentation(this) - - private fun PsiMethod.isObvious(inheritedFrom: DRI? = null): Boolean { - return (this is SyntheticElement && !syntheticDocProvider.isDocumented(this)) - || inheritedFrom?.isObvious() == true - } - - private fun DRI.isObvious(): Boolean { - return packageName == "java.lang" && (classNames == "Object" || classNames == "Enum") - } - - private fun PsiReferenceList.toDriList() = referenceElements.mapNotNull { it?.resolve()?.let { DRI.from(it) } } - - private fun PsiModifierListOwner.additionalExtras() = listOfNotNull( - ExtraModifiers.JavaOnlyModifiers.Static.takeIf { hasModifier(JvmModifier.STATIC) }, - ExtraModifiers.JavaOnlyModifiers.Native.takeIf { hasModifier(JvmModifier.NATIVE) }, - ExtraModifiers.JavaOnlyModifiers.Synchronized.takeIf { hasModifier(JvmModifier.SYNCHRONIZED) }, - ExtraModifiers.JavaOnlyModifiers.StrictFP.takeIf { hasModifier(JvmModifier.STRICTFP) }, - ExtraModifiers.JavaOnlyModifiers.Transient.takeIf { hasModifier(JvmModifier.TRANSIENT) }, - ExtraModifiers.JavaOnlyModifiers.Volatile.takeIf { hasModifier(JvmModifier.VOLATILE) }, - ExtraModifiers.JavaOnlyModifiers.Transitive.takeIf { hasModifier(JvmModifier.TRANSITIVE) } - ).toSet() - - private fun Set<ExtraModifiers>.toListOfAnnotations() = map { - if (it !is ExtraModifiers.JavaOnlyModifiers.Static) - Annotations.Annotation(DRI("kotlin.jvm", it.name.toLowerCase().capitalize()), emptyMap()) - else - Annotations.Annotation(DRI("kotlin.jvm", "JvmStatic"), emptyMap()) - } - - /** - * Workaround for getting JvmField Kotlin annotation in PSIs - */ - private fun Collection<PsiAnnotation>.findJvmFieldAnnotation(): Annotations.Annotation? { - val anyJvmFieldAnnotation = this.any { - it.qualifiedName == "$JVM_FIELD_PACKAGE_NAME.$JVM_FIELD_CLASS_NAMES" - } - return if (anyJvmFieldAnnotation) { - Annotations.Annotation(DRI(JVM_FIELD_PACKAGE_NAME, JVM_FIELD_CLASS_NAMES), emptyMap()) - } else { - null - } - } - - private fun <T : AnnotationTarget> PsiTypeParameter.annotations(): PropertyContainer<T> = this.annotations.toList().toListOfAnnotations().annotations() - private fun <T : AnnotationTarget> PsiType.annotations(): PropertyContainer<T> = this.annotations.toList().toListOfAnnotations().annotations() - - private fun <T : AnnotationTarget> List<Annotations.Annotation>.annotations(): PropertyContainer<T> = - this.takeIf { it.isNotEmpty() }?.let { annotations -> - PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations()) - } ?: PropertyContainer.empty() - - private fun getBound(type: PsiType): Bound { - //We would like to cache most of the bounds since it is not common to annotate them, - //but if this is the case, we treat them as 'one of' - fun PsiType.cacheBoundIfHasNoAnnotation(f: (List<Annotations.Annotation>) -> Bound): Bound { - val annotations = this.annotations.toList().toListOfAnnotations() - return if (annotations.isNotEmpty()) f(annotations) - else cachedBounds.getOrPut(canonicalText) { - f(annotations) - } - } - - return when (type) { - is PsiClassType -> - type.resolve()?.let { resolved -> - when { - resolved.qualifiedName == "java.lang.Object" -> type.cacheBoundIfHasNoAnnotation { annotations -> JavaObject(annotations.annotations()) } - resolved is PsiTypeParameter -> { - TypeParameter( - dri = DRI.from(resolved), - name = resolved.name.orEmpty(), - extra = type.annotations() - ) - } - - Regex("kotlin\\.jvm\\.functions\\.Function.*").matches(resolved.qualifiedName ?: "") || - Regex("java\\.util\\.function\\.Function.*").matches( - resolved.qualifiedName ?: "" - ) -> FunctionalTypeConstructor( - DRI.from(resolved), - type.parameters.map { getProjection(it) }, - extra = type.annotations() - ) - - else -> { - // cache types that have no annotation and no type parameter - // since we cache only by name and type parameters depend on context - val typeParameters = type.parameters.map { getProjection(it) } - if (typeParameters.isEmpty()) - type.cacheBoundIfHasNoAnnotation { annotations -> - GenericTypeConstructor( - DRI.from(resolved), - typeParameters, - extra = annotations.annotations() - ) - } - else - GenericTypeConstructor( - DRI.from(resolved), - typeParameters, - extra = type.annotations() - ) - } - } - } ?: UnresolvedBound(type.presentableText, type.annotations()) - - is PsiArrayType -> GenericTypeConstructor( - DRI("kotlin", "Array"), - listOf(getProjection(type.componentType)), - extra = type.annotations() - ) - - is PsiPrimitiveType -> if (type.name == "void") Void - else type.cacheBoundIfHasNoAnnotation { annotations -> PrimitiveJavaType(type.name, annotations.annotations()) } - else -> throw IllegalStateException("${type.presentableText} is not supported by PSI parser") - } - } - - - private fun getVariance(type: PsiWildcardType): Projection = when { - type.extendsBound != PsiType.NULL -> Covariance(getBound(type.extendsBound)) - type.superBound != PsiType.NULL -> Contravariance(getBound(type.superBound)) - else -> throw IllegalStateException("${type.presentableText} has incorrect bounds") - } - - private fun getProjection(type: PsiType): Projection = when (type) { - is PsiEllipsisType -> Star - is PsiWildcardType -> getVariance(type) - else -> getBound(type) - } - - private fun PsiModifierListOwner.getModifier() = when { - hasModifier(JvmModifier.ABSTRACT) -> JavaModifier.Abstract - hasModifier(JvmModifier.FINAL) -> JavaModifier.Final - else -> JavaModifier.Empty - } - - private fun PsiTypeParameterListOwner.mapTypeParameters(dri: DRI): List<DTypeParameter> { - fun mapBounds(bounds: Array<JvmReferenceType>): List<Bound> = - if (bounds.isEmpty()) emptyList() else bounds.mapNotNull { - (it as? PsiClassType)?.let { classType -> Nullable(getBound(classType)) } - } - return typeParameters.map { type -> - DTypeParameter( - dri = dri.copy(target = dri.target.nextTarget()), - name = type.name.orEmpty(), - presentableName = null, - documentation = javadocParser.parseDocumentation(type).toSourceSetDependent(), - expectPresentInSet = null, - bounds = mapBounds(type.bounds), - sourceSets = setOf(sourceSetData), - extra = PropertyContainer.withAll( - type.annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() - ) - ) - } - } - - private fun parseFieldWithInheritingAccessors( - psi: PsiField, - accessors: List<Pair<PsiMethod, DRI>>, - inheritedFrom: DRI - ): DProperty { - val getter = accessors - .firstOrNull { (method, _) -> method.isGetterFor(psi) } - ?.let { (method, dri) -> parseFunction(method, inheritedFrom = dri) } - - val setter = accessors - .firstOrNull { (method, _) -> method.isSetterFor(psi) } - ?.let { (method, dri) -> parseFunction(method, inheritedFrom = dri) } - - return parseField( - psi = psi, - getter = getter, - setter = setter, - inheritedFrom = inheritedFrom - ) - } - - private fun parseField(psi: PsiField, accessors: List<PsiMethod>, inheritedFrom: DRI? = null): DProperty { - val getter = accessors.firstOrNull { it.isGetterFor(psi) }?.let { parseFunction(it) } - val setter = accessors.firstOrNull { it.isSetterFor(psi) }?.let { parseFunction(it) } - return parseField( - psi = psi, - getter = getter, - setter = setter, - inheritedFrom = inheritedFrom - ) - } - - private fun parseField(psi: PsiField, getter: DFunction?, setter: DFunction?, inheritedFrom: DRI? = null): DProperty { - val dri = DRI.from(psi) - - // non-final java field without accessors should be a var - // setter should be not null when inheriting kotlin vars - val isMutable = !psi.hasModifierProperty("final") - val isVar = (isMutable && getter == null && setter == null) || (getter != null && setter != null) - - return DProperty( - dri = dri, - name = psi.name, - documentation = javadocParser.parseDocumentation(psi).toSourceSetDependent(), - expectPresentInSet = null, - sources = psi.parseSources(), - visibility = psi.getVisibility(getter).toSourceSetDependent(), - type = getBound(psi.type), - receiver = null, - setter = setter, - getter = getter, - modifier = psi.getModifier().toSourceSetDependent(), - sourceSets = setOf(sourceSetData), - generics = emptyList(), - isExpectActual = false, - extra = psi.additionalExtras().let { - val psiAnnotations = psi.annotations.toList() - val parsedAnnotations = psiAnnotations.toListOfAnnotations() - val extraModifierAnnotations = it.toListOfAnnotations() - val jvmFieldAnnotation = psiAnnotations.findJvmFieldAnnotation() - val annotations = parsedAnnotations + extraModifierAnnotations + listOfNotNull(jvmFieldAnnotation) - - PropertyContainer.withAll( - inheritedFrom?.let { inheritedFrom -> InheritedMember(inheritedFrom.toSourceSetDependent()) }, - it.toSourceSetDependent().toAdditionalModifiers(), - annotations.toSourceSetDependent().toAnnotations(), - psi.getConstantExpression()?.let { DefaultValue(it.toSourceSetDependent()) }, - takeIf { isVar }?.let { IsVar } - ) - } - ) - } - - private fun PsiField.getVisibility(getter: DFunction?): Visibility { - return getter?.visibility?.get(sourceSetData) ?: this.getVisibility() - } - - private fun Collection<PsiAnnotation>.toListOfAnnotations() = - filter { it !is KtLightAbstractAnnotation }.mapNotNull { it.toAnnotation() } - - private fun PsiField.getConstantExpression(): Expression? { - val constantValue = this.computeConstantValue() ?: return null - return when (constantValue) { - is Byte -> IntegerConstant(constantValue.toLong()) - is Short -> IntegerConstant(constantValue.toLong()) - is Int -> IntegerConstant(constantValue.toLong()) - is Long -> IntegerConstant(constantValue) - is Char -> StringConstant(constantValue.toString()) - is String -> StringConstant(constantValue) - is Double -> DoubleConstant(constantValue) - is Float -> FloatConstant(constantValue) - is Boolean -> BooleanConstant(constantValue) - else -> ComplexExpression(constantValue.toString()) - } - } - - private fun JvmAnnotationAttribute.toValue(): AnnotationParameterValue = when (this) { - is PsiNameValuePair -> value?.toValue() ?: attributeValue?.toValue() ?: StringValue("") - else -> StringValue(this.attributeName) - }.let { annotationValue -> - if (annotationValue is StringValue) annotationValue.copy(unquotedValue(annotationValue.value)) - else annotationValue - } - - /** - * This is a workaround for static imports from JDK like RetentionPolicy - * For some reason they are not represented in the same way than using normal import - */ - private fun JvmAnnotationAttributeValue.toValue(): AnnotationParameterValue? { - return when (this) { - is JvmAnnotationEnumFieldValue -> (field as? PsiElement)?.let { EnumValue(fieldName ?: "", DRI.from(it)) } - // static import of a constant is resolved to constant value instead of a field/link - is JvmAnnotationConstantValue -> this.constantValue?.toAnnotationLiteralValue() - else -> null - } - } - - private fun Any.toAnnotationLiteralValue() = when (this) { - is Byte -> IntValue(this.toInt()) - is Short -> IntValue(this.toInt()) - is Char -> StringValue(this.toString()) - is Int -> IntValue(this) - is Long -> LongValue(this) - is Boolean -> BooleanValue(this) - is Float -> FloatValue(this) - is Double -> DoubleValue(this) - else -> StringValue(this.toString()) - } - - private fun PsiAnnotationMemberValue.toValue(): AnnotationParameterValue? = when (this) { - is PsiAnnotation -> toAnnotation()?.let { AnnotationValue(it) } - is PsiArrayInitializerMemberValue -> ArrayValue(initializers.mapNotNull { it.toValue() }) - is PsiReferenceExpression -> psiReference?.let { EnumValue(text ?: "", DRI.from(it)) } - is PsiClassObjectAccessExpression -> { - val parameterType = (type as? PsiClassType)?.parameters?.firstOrNull() - val classType = when (parameterType) { - is PsiClassType -> parameterType.resolve() - // Notice: Array<String>::class will be passed down as String::class - // should probably be Array::class instead but this reflects behaviour for Kotlin sources - is PsiArrayType -> (parameterType.componentType as? PsiClassType)?.resolve() - else -> null - } - classType?.let { ClassValue(it.name ?: "", DRI.from(it)) } - } - is PsiLiteralExpression -> toValue() - else -> StringValue(text ?: "") - } - - private fun PsiLiteralExpression.toValue(): AnnotationParameterValue? = when (type) { - PsiType.INT -> (value as? Int)?.let { IntValue(it) } - PsiType.LONG -> (value as? Long)?.let { LongValue(it) } - PsiType.FLOAT -> (value as? Float)?.let { FloatValue(it) } - PsiType.DOUBLE -> (value as? Double)?.let { DoubleValue(it) } - PsiType.BOOLEAN -> (value as? Boolean)?.let { BooleanValue(it) } - PsiType.NULL -> NullValue - else -> StringValue(text ?: "") - } - - private fun PsiAnnotation.toAnnotation(): Annotations.Annotation? { - // TODO Mitigating workaround for issue https://github.com/Kotlin/dokka/issues/1341 - // Tracking https://youtrack.jetbrains.com/issue/KT-41234 - // Needs to be removed once this issue is fixed in light classes - fun PsiElement.getAnnotationsOrNull(): Array<PsiAnnotation>? { - this as PsiClass - return try { - this.annotations - } catch (e: KotlinExceptionWithAttachments) { - logger.warn("Failed to get annotations from ${this.getKotlinFqName()}") - null - } - } - - return psiReference?.let { psiElement -> - Annotations.Annotation( - dri = DRI.from(psiElement), - params = attributes - .filter { it !is KtLightAbstractAnnotation } - .mapNotNull { it.attributeName to it.toValue() } - .toMap(), - mustBeDocumented = psiElement.getAnnotationsOrNull().orEmpty().any { annotation -> - annotation.hasQualifiedName("java.lang.annotation.Documented") - } - ) - } - } - - private val PsiElement.psiReference - get() = getChildOfType<PsiJavaCodeReferenceElement>()?.resolve() - } -} - -internal fun PsiModifierListOwner.getVisibility() = modifierList?.let { - val ml = it.children.toList() - when { - ml.any { it.text == PsiKeyword.PUBLIC } || it.hasModifierProperty("public") -> JavaVisibility.Public - ml.any { it.text == PsiKeyword.PROTECTED } || it.hasModifierProperty("protected") -> JavaVisibility.Protected - ml.any { it.text == PsiKeyword.PRIVATE } || it.hasModifierProperty("private") -> JavaVisibility.Private - else -> JavaVisibility.Default - } -} ?: JavaVisibility.Default diff --git a/plugins/base/src/main/kotlin/translators/psi/PsiAccessorConventionUtil.kt b/plugins/base/src/main/kotlin/translators/psi/PsiAccessorConventionUtil.kt deleted file mode 100644 index 3c1cb2cf..00000000 --- a/plugins/base/src/main/kotlin/translators/psi/PsiAccessorConventionUtil.kt +++ /dev/null @@ -1,94 +0,0 @@ -package org.jetbrains.dokka.base.translators.psi - -import com.intellij.psi.PsiField -import com.intellij.psi.PsiMethod -import org.jetbrains.dokka.base.translators.firstNotNullOfOrNull -import org.jetbrains.dokka.model.JavaVisibility -import org.jetbrains.dokka.model.KotlinVisibility -import org.jetbrains.dokka.model.Visibility -import org.jetbrains.kotlin.load.java.JvmAbi -import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName -import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName -import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.resolve.DescriptorUtils - - -internal data class PsiFunctionsHolder( - val regularFunctions: List<PsiMethod>, - val accessors: Map<PsiField, List<PsiMethod>> -) - -internal fun splitFunctionsAndAccessors(fields: Array<PsiField>, methods: Array<PsiMethod>): PsiFunctionsHolder { - val fieldsByName = fields.associateBy { it.name } - val regularFunctions = mutableListOf<PsiMethod>() - val accessors = mutableMapOf<PsiField, MutableList<PsiMethod>>() - methods.forEach { method -> - val possiblePropertyNamesForFunction = method.getPossiblePropertyNamesForFunction() - val field = possiblePropertyNamesForFunction.firstNotNullOfOrNull { fieldsByName[it] } - if (field != null && method.isAccessorFor(field)) { - accessors.getOrPut(field, ::mutableListOf).add(method) - } else { - regularFunctions.add(method) - } - } - - val accessorLookalikes = removeNonAccessorsReturning(accessors) - regularFunctions.addAll(accessorLookalikes) - - return PsiFunctionsHolder(regularFunctions, accessors) -} - -/** - * If a field has no getter, it's not accessible as a property from Kotlin's perspective, - * but it still might have a setter. In this case, this "setter" should be just a regular function - */ -private fun removeNonAccessorsReturning( - fieldAccessors: MutableMap<PsiField, MutableList<PsiMethod>> -): List<PsiMethod> { - val nonAccessors = mutableListOf<PsiMethod>() - fieldAccessors.entries.removeIf { (field, methods) -> - if (methods.size == 1 && methods[0].isSetterFor(field)) { - nonAccessors.add(methods[0]) - true - } else { - false - } - } - return nonAccessors -} - -internal fun PsiMethod.getPossiblePropertyNamesForFunction(): List<String> { - val jvmName = getAnnotation(DescriptorUtils.JVM_NAME.asString())?.findAttributeValue("name")?.text - return jvmName?.let { listOf(jvmName) } - ?: when { - JvmAbi.isGetterName(name) -> listOfNotNull( - propertyNameByGetMethodName(Name.identifier(name))?.asString() - ) - JvmAbi.isSetterName(name) -> { - propertyNamesBySetMethodName(Name.identifier(name)).map { it.asString() } - } - else -> listOf() - } -} - -internal fun PsiMethod.isAccessorFor(field: PsiField): Boolean { - return (this.isGetterFor(field) || this.isSetterFor(field)) - && !field.getVisibility().isPublicAPI() - && this.getVisibility().isPublicAPI() -} - -internal fun PsiMethod.isGetterFor(field: PsiField): Boolean { - return this.returnType == field.type && !this.hasParameters() -} - -internal fun PsiMethod.isSetterFor(field: PsiField): Boolean { - return parameterList.getParameter(0)?.type == field.type && parameterList.getParametersCount() == 1 -} - -internal fun Visibility.isPublicAPI() = when(this) { - KotlinVisibility.Public, - KotlinVisibility.Protected, - JavaVisibility.Public, - JavaVisibility.Protected -> true - else -> false -} diff --git a/plugins/base/src/main/kotlin/translators/psi/PsiInheritance.kt b/plugins/base/src/main/kotlin/translators/psi/PsiInheritance.kt deleted file mode 100644 index 1eca489e..00000000 --- a/plugins/base/src/main/kotlin/translators/psi/PsiInheritance.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.jetbrains.dokka.base.translators.psi - -import com.intellij.psi.PsiClass -import com.intellij.psi.PsiMethod -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.kotlin.idea.base.utils.fqname.getKotlinFqName -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.utils.addToStdlib.safeAs - -internal fun PsiClass.implementsInterface(fqName: FqName): Boolean { - return allInterfaces().any { it.getKotlinFqName() == fqName } -} - -internal fun PsiClass.allInterfaces(): Sequence<PsiClass> { - return sequence { - this.yieldAll(interfaces.toList()) - interfaces.forEach { yieldAll(it.allInterfaces()) } - } -} - -/** - * Workaround for failing [PsiMethod.findSuperMethods]. - * This might be resolved once ultra light classes are enabled for dokka - * See [KT-39518](https://youtrack.jetbrains.com/issue/KT-39518) - */ -internal fun PsiMethod.findSuperMethodsOrEmptyArray(logger: DokkaLogger): Array<PsiMethod> { - return try { - /* - We are not even attempting to call "findSuperMethods" on all methods called "getGetter" or "getSetter" - on any object implementing "kotlin.reflect.KProperty", since we know that those methods will fail - (KT-39518). Just catching the exception is not good enough, since "findSuperMethods" will - print the whole exception to stderr internally and then spoil the console. - */ - val kPropertyFqName = FqName("kotlin.reflect.KProperty") - if ( - this.parent?.safeAs<PsiClass>()?.implementsInterface(kPropertyFqName) == true && - (this.name == "getSetter" || this.name == "getGetter") - ) { - logger.warn("Skipped lookup of super methods for ${getKotlinFqName()} (KT-39518)") - return emptyArray() - } - findSuperMethods() - } catch (exception: Throwable) { - logger.warn("Failed to lookup of super methods for ${getKotlinFqName()} (KT-39518)") - emptyArray() - } -}
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt deleted file mode 100644 index 376c0940..00000000 --- a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.jetbrains.dokka.base.translators.psi - -import com.intellij.psi.* -import com.intellij.psi.javadoc.PsiDocComment -import org.jetbrains.dokka.analysis.DokkaResolutionFacade -import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser -import org.jetbrains.dokka.model.doc.DocumentationNode - -private const val ENUM_VALUEOF_TEMPLATE_PATH = "/dokka/docs/javadoc/EnumValueOf.java.template" -private const val ENUM_VALUES_TEMPLATE_PATH = "/dokka/docs/javadoc/EnumValues.java.template" - -internal class SyntheticElementDocumentationProvider( - private val javadocParser: JavadocParser, - private val resolutionFacade: DokkaResolutionFacade -) { - fun isDocumented(psiElement: PsiElement): Boolean = psiElement is PsiMethod - && (psiElement.isSyntheticEnumValuesMethod() || psiElement.isSyntheticEnumValueOfMethod()) - - fun getDocumentation(psiElement: PsiElement): DocumentationNode? { - val psiMethod = psiElement as? PsiMethod ?: return null - val templatePath = when { - psiMethod.isSyntheticEnumValuesMethod() -> ENUM_VALUES_TEMPLATE_PATH - psiMethod.isSyntheticEnumValueOfMethod() -> ENUM_VALUEOF_TEMPLATE_PATH - else -> return null - } - val docComment = loadSyntheticDoc(templatePath) ?: return null - return javadocParser.parseDocComment(docComment, psiElement) - } - - private fun loadSyntheticDoc(path: String): PsiDocComment? { - val text = javaClass.getResource(path)?.readText() ?: return null - return JavaPsiFacade.getElementFactory(resolutionFacade.project).createDocCommentFromText(text) - } -} - -private fun PsiMethod.isSyntheticEnumValuesMethod() = this.isSyntheticEnumFunction() && this.name == "values" -private fun PsiMethod.isSyntheticEnumValueOfMethod() = this.isSyntheticEnumFunction() && this.name == "valueOf" -private fun PsiMethod.isSyntheticEnumFunction() = this is SyntheticElement && this.containingClass?.isEnum == true - diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/InheritDocResolver.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/InheritDocResolver.kt index e7f8c9ec..e69de29b 100644 --- a/plugins/base/src/main/kotlin/translators/psi/parsers/InheritDocResolver.kt +++ b/plugins/base/src/main/kotlin/translators/psi/parsers/InheritDocResolver.kt @@ -1,129 +0,0 @@ -package org.jetbrains.dokka.base.translators.psi.parsers - -import com.intellij.psi.PsiClass -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiMethod -import com.intellij.psi.javadoc.PsiDocComment -import com.intellij.psi.javadoc.PsiDocTag -import org.jetbrains.dokka.utilities.DokkaLogger - -internal data class CommentResolutionContext( - val comment: PsiDocComment, - val tag: JavadocTag?, - val name: String? = null, - val parameterIndex: Int? = null, -) - -internal class InheritDocResolver( - private val logger: DokkaLogger -) { - internal fun resolveFromContext(context: CommentResolutionContext) = - when (context.tag) { - JavadocTag.THROWS, JavadocTag.EXCEPTION -> context.name?.let { name -> - resolveThrowsTag( - context.tag, - context.comment, - name - ) - } - JavadocTag.PARAM -> context.parameterIndex?.let { paramIndex -> - resolveParamTag( - context.comment, - paramIndex - ) - } - JavadocTag.DEPRECATED -> resolveGenericTag(context.comment, JavadocTag.DESCRIPTION) - JavadocTag.SEE -> emptyList() - else -> context.tag?.let { tag -> resolveGenericTag(context.comment, tag) } - } - - private fun resolveGenericTag(currentElement: PsiDocComment, tag: JavadocTag) = - when (val owner = currentElement.owner) { - is PsiClass -> lowestClassWithTag(owner, tag) - is PsiMethod -> lowestMethodWithTag(owner, tag) - else -> null - }?.tagsByName(tag)?.flatMap { - when { - it is PsiDocumentationContent && it.psiElement is PsiDocTag -> - it.psiElement.contentElementsWithSiblingIfNeeded() - .map { content -> PsiDocumentationContent(content, it.tag) } - else -> listOf(it) - } - }.orEmpty() - - /** - * Main resolution point for exception like tags - * - * This should be used only with [JavadocTag.EXCEPTION] or [JavadocTag.THROWS] as their resolution path should be the same - */ - private fun resolveThrowsTag( - tag: JavadocTag, - currentElement: PsiDocComment, - exceptionFqName: String - ): List<DocumentationContent> { - val closestDocs = (currentElement.owner as? PsiMethod)?.let { method -> lowestMethodsWithTag(method, tag) } - .orEmpty().firstOrNull { - findClosestDocComment(it, logger)?.hasTagWithExceptionOfType(tag, exceptionFqName) == true - } - - return when (closestDocs?.language?.id) { - "kotlin" -> closestDocs.toKdocComment()?.tagsByName(tag, exceptionFqName).orEmpty() - else -> closestDocs?.docComment?.tagsByName(tag)?.flatMap { - when (it) { - is PsiDocTag -> it.contentElementsWithSiblingIfNeeded() - else -> listOf(it) - } - }?.withoutReferenceLink().orEmpty().map { PsiDocumentationContent(it, tag) } - } - } - - private fun resolveParamTag( - currentElement: PsiDocComment, - parameterIndex: Int, - ): List<DocumentationContent> = - (currentElement.owner as? PsiMethod)?.let { method -> lowestMethodsWithTag(method, JavadocTag.PARAM) } - .orEmpty().flatMap { - if (parameterIndex >= it.parameterList.parametersCount || parameterIndex < 0) emptyList() - else { - val closestTag = findClosestDocComment(it, logger) - val hasTag = closestTag?.hasTag(JavadocTag.PARAM) - when { - hasTag != true -> emptyList() - closestTag is JavaDocComment -> resolveJavaParamTag(closestTag, parameterIndex, it) - .withoutReferenceLink().map { PsiDocumentationContent(it, JavadocTag.PARAM) } - closestTag is KotlinDocComment -> resolveKdocTag(closestTag, parameterIndex) - else -> emptyList() - } - } - } - - private fun resolveJavaParamTag(comment: JavaDocComment, parameterIndex: Int, method: PsiMethod) = - comment.comment.tagsByName(JavadocTag.PARAM) - .filterIsInstance<PsiDocTag>().map { it.contentElementsWithSiblingIfNeeded() }.firstOrNull { - it.firstOrNull()?.text == method.parameterList.parameters[parameterIndex].name - }.orEmpty() - - private fun resolveKdocTag(comment: KotlinDocComment, parameterIndex: Int): List<DocumentationContent> = - listOf(comment.tagsByName(JavadocTag.PARAM)[parameterIndex]) - - //if we are in psi class javadoc only inherits docs from classes and not from interfaces - private fun lowestClassWithTag(baseClass: PsiClass, javadocTag: JavadocTag): DocComment? = - baseClass.superClass?.let { - findClosestDocComment(it, logger)?.takeIf { tag -> tag.hasTag(javadocTag) } ?: lowestClassWithTag( - it, - javadocTag - ) - } - - private fun lowestMethodWithTag( - baseMethod: PsiMethod, - javadocTag: JavadocTag, - ): DocComment? = - lowestMethodsWithTag(baseMethod, javadocTag).firstOrNull() - ?.let { it.docComment?.let { JavaDocComment(it) } ?: it.toKdocComment() } - - private fun lowestMethodsWithTag(baseMethod: PsiMethod, javadocTag: JavadocTag) = - baseMethod.findSuperMethods().filter { findClosestDocComment(it, logger)?.hasTag(javadocTag) == true } - - private fun List<PsiElement>.withoutReferenceLink(): List<PsiElement> = drop(1) -} diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt index 59c6f702..e69de29b 100644 --- a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt +++ b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt @@ -1,511 +0,0 @@ -package org.jetbrains.dokka.base.translators.psi.parsers - -import com.intellij.lexer.JavaDocTokenTypes -import com.intellij.psi.* -import com.intellij.psi.impl.source.javadoc.PsiDocParamRef -import com.intellij.psi.impl.source.tree.JavaDocElementType -import com.intellij.psi.impl.source.tree.LazyParseablePsiElement -import com.intellij.psi.impl.source.tree.LeafPsiElement -import com.intellij.psi.javadoc.* -import org.intellij.markdown.MarkdownElementTypes -import org.jetbrains.dokka.analysis.DokkaResolutionFacade -import org.jetbrains.dokka.analysis.from -import org.jetbrains.dokka.base.parsers.MarkdownParser -import org.jetbrains.dokka.base.translators.parseHtmlEncodedWithNormalisedSpaces -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.doc.* -import org.jetbrains.dokka.model.doc.Deprecated -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.dokka.utilities.htmlEscape -import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink -import org.jetbrains.kotlin.idea.base.utils.fqname.getKotlinFqName -import org.jetbrains.kotlin.idea.util.CommentSaver.Companion.tokenType -import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespace -import org.jetbrains.kotlin.psi.psiUtil.siblings -import org.jsoup.Jsoup -import org.jsoup.nodes.Comment -import org.jsoup.nodes.Element -import org.jsoup.nodes.Node -import org.jsoup.nodes.TextNode -import java.util.* - -fun interface JavaDocumentationParser { - fun parseDocumentation(element: PsiNamedElement): DocumentationNode -} - -class JavadocParser( - private val logger: DokkaLogger, - private val resolutionFacade: DokkaResolutionFacade, -) : JavaDocumentationParser { - private val inheritDocResolver = InheritDocResolver(logger) - - /** - * Cache created to make storing entries from kotlin easier. - * - * It has to be mutable to allow for adding entries when @inheritDoc resolves to kotlin code, - * from which we get a DocTags not descriptors. - */ - private var inheritDocSections: MutableMap<UUID, DocumentationNode> = mutableMapOf() - - override fun parseDocumentation(element: PsiNamedElement): DocumentationNode { - return when(val comment = findClosestDocComment(element, logger)){ - is JavaDocComment -> parseDocComment(comment.comment, element) - is KotlinDocComment -> parseDocumentation(comment) - else -> DocumentationNode(emptyList()) - } - } - - internal fun parseDocComment(docComment: PsiDocComment, context: PsiNamedElement): DocumentationNode { - val nodes = listOfNotNull(docComment.getDescription()) + docComment.tags.mapNotNull { tag -> - parseDocTag(tag, docComment, context) - } - return DocumentationNode(nodes) - } - - private fun parseDocumentation(element: KotlinDocComment, parseWithChildren: Boolean = true): DocumentationNode = - MarkdownParser.parseFromKDocTag( - kDocTag = element.comment, - externalDri = { link: String -> - try { - resolveKDocLink( - context = resolutionFacade.resolveSession.bindingContext, - resolutionFacade = resolutionFacade, - fromDescriptor = element.descriptor, - fromSubjectOfTag = null, - qualifiedName = link.split('.') - ).firstOrNull()?.let { DRI.from(it) } - } catch (e1: IllegalArgumentException) { - logger.warn("Couldn't resolve link for $link") - null - } - }, - kdocLocation = null, - parseWithChildren = parseWithChildren - ) - - private fun parseDocTag(tag: PsiDocTag, docComment: PsiDocComment, analysedElement: PsiNamedElement): TagWrapper { - val javadocTag = JavadocTag.lowercaseValueOfOrNull(tag.name) - if (javadocTag == null) { - return emptyTagWrapper(tag, docComment) - } - // Javadoc tag found - val resolutionContext = CommentResolutionContext(comment = docComment, tag = javadocTag) - return when (resolutionContext.tag) { - JavadocTag.PARAM -> { - val name = tag.dataElements.firstOrNull()?.text.orEmpty() - val index = - (analysedElement as? PsiMethod)?.parameterList?.parameters?.map { it.name }?.indexOf(name) - Param( - wrapTagIfNecessary( - convertJavadocElements( - tag.contentElementsWithSiblingIfNeeded().drop(1), - context = resolutionContext.copy(name = name, parameterIndex = index) - ) - ), - name - ) - } - - JavadocTag.THROWS, JavadocTag.EXCEPTION -> { - val resolved = tag.resolveToElement() - val dri = resolved?.let { DRI.from(it) } - val name = resolved?.getKotlinFqName()?.asString() - ?: tag.dataElements.firstOrNull()?.text.orEmpty() - Throws( - root = wrapTagIfNecessary( - convertJavadocElements( - tag.dataElements.drop(1), - context = resolutionContext.copy(name = name) - ) - ), - /* we always would like to have a fully qualified name as name, - * because it will be used as a display name later and we would like to have those unified - * even if documentation states shortened version - * - * Only if dri search fails we should use the provided phrase (since then we are not able to get a fq name) - * */ - name = name, - exceptionAddress = dri - ) - } - - JavadocTag.RETURN -> Return( - wrapTagIfNecessary( - convertJavadocElements( - tag.contentElementsWithSiblingIfNeeded(), - context = resolutionContext - ) - ) - ) - - JavadocTag.AUTHOR -> Author( - wrapTagIfNecessary( - convertJavadocElements( - tag.contentElementsWithSiblingIfNeeded(), - context = resolutionContext - ) - ) - ) // Workaround: PSI returns first word after @author tag as a `DOC_TAG_VALUE_ELEMENT`, then the rest as a `DOC_COMMENT_DATA`, so for `Name Surname` we get them parted - JavadocTag.SEE -> { - val name = - tag.resolveToElement()?.getKotlinFqName()?.asString() ?: tag.referenceElement()?.text.orEmpty() - .removePrefix("#") - getSeeTagElementContent(tag, resolutionContext.copy(name = name)).let { - See( - wrapTagIfNecessary(it.first), - name, - it.second - ) - } - } - - JavadocTag.DEPRECATED -> Deprecated( - wrapTagIfNecessary( - convertJavadocElements( - tag.contentElementsWithSiblingIfNeeded(), - context = resolutionContext - ) - ) - ) - - JavadocTag.SINCE -> Since( - wrapTagIfNecessary( - convertJavadocElements( - tag.contentElementsWithSiblingIfNeeded(), - context = resolutionContext - ) - ) - ) - - else -> emptyTagWrapper(tag, docComment) - } - } - - // Wrapper for unsupported tags https://github.com/Kotlin/dokka/issues/1618 - private fun emptyTagWrapper( - tag: PsiDocTag, - docComment: PsiDocComment - ) = CustomTagWrapper( - wrapTagIfNecessary( - convertJavadocElements( - tag.contentElementsWithSiblingIfNeeded(), - context = CommentResolutionContext(docComment, null) - )), tag.name - ) - - private fun wrapTagIfNecessary(list: List<DocTag>): CustomDocTag = - if (list.size == 1 && (list.first() as? CustomDocTag)?.name == MarkdownElementTypes.MARKDOWN_FILE.name) - list.first() as CustomDocTag - else - CustomDocTag(list, name = MarkdownElementTypes.MARKDOWN_FILE.name) - - private fun getSeeTagElementContent( - tag: PsiDocTag, - context: CommentResolutionContext - ): Pair<List<DocTag>, DRI?> { - val referenceElement = tag.referenceElement() - val linkElement = referenceElement?.toDocumentationLink(context = context) - val content = convertJavadocElements( - tag.dataElements.dropWhile { it is PsiWhiteSpace || (it as? LazyParseablePsiElement)?.tokenType == JavaDocElementType.DOC_REFERENCE_HOLDER || it == referenceElement }, - context = context - ) - return Pair(content, linkElement?.dri) - } - - private fun PsiDocComment.getDescription(): Description? { - return convertJavadocElements( - descriptionElements.asIterable(), - context = CommentResolutionContext(this, JavadocTag.DESCRIPTION) - ).takeIf { it.isNotEmpty() }?.let { - Description(wrapTagIfNecessary(it)) - } - } - - private data class ParserState( - val currentJavadocTag: JavadocTag?, - val previousElement: PsiElement? = null, - val openPreTags: Int = 0, - val closedPreTags: Int = 0 - ) - - private data class ParsingResult(val newState: ParserState, val parsedLine: String? = null) { - constructor(tag: JavadocTag?) : this(ParserState(tag)) - - operator fun plus(other: ParsingResult): ParsingResult = - ParsingResult( - other.newState, - listOfNotNull(parsedLine, other.parsedLine).joinToString(separator = "") - ) - } - - private inner class Parse : (Iterable<PsiElement>, Boolean, CommentResolutionContext) -> List<DocTag> { - val driMap = mutableMapOf<String, DRI>() - - private fun PsiElement.stringify(state: ParserState, context: CommentResolutionContext): ParsingResult = - when (this) { - is PsiReference -> children.fold(ParsingResult(state)) { acc, e -> - acc + e.stringify(acc.newState, context) - } - else -> stringifySimpleElement(state, context) - } - - private fun DocumentationContent.stringify(state: ParserState, context: CommentResolutionContext): ParsingResult = - when(this){ - is PsiDocumentationContent -> psiElement.stringify(state, context) - is DescriptorDocumentationContent -> { - val id = UUID.randomUUID() - inheritDocSections[id] = parseDocumentation(KotlinDocComment(element, descriptor), parseWithChildren = false) - ParsingResult(state, """<inheritdoc id="$id"/>""") - } - else -> throw IllegalStateException("Unrecognised documentation content: $this") - } - - private fun PsiElement.stringifySimpleElement( - state: ParserState, - context: CommentResolutionContext - ): ParsingResult { - val openPre = state.openPreTags + "<pre(\\s+.*)?>".toRegex().findAll(text).toList().size - val closedPre = state.closedPreTags + "</pre>".toRegex().findAll(text).toList().size - val isInsidePre = openPre > closedPre - val parsed = when (this) { - is PsiInlineDocTag -> convertInlineDocTag(this, state.currentJavadocTag, context) - is PsiDocParamRef -> toDocumentationLinkString() - is PsiDocTagValue, - is LeafPsiElement -> stringifyElementAsText(isInsidePre, state.previousElement) - else -> null - } - val previousElement = if (text.trim() == "") state.previousElement else this - return ParsingResult( - state.copy( - previousElement = previousElement, - closedPreTags = closedPre, - openPreTags = openPre - ), parsed - ) - } - - private fun PsiElement.stringifyElementAsText(keepFormatting: Boolean, previousElement: PsiElement? = null) = if (keepFormatting) { - /* - For values in the <pre> tag we try to keep formatting, so only the leading space is trimmed, - since it is there because it separates this line from the leading asterisk - */ - text.let { - if (((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true || (prevSibling as? PsiDocToken)?.isTagName() == true ) && it.firstOrNull() == ' ') - it.drop(1) else it - }.let { - if ((nextSibling as? PsiDocToken)?.isLeadingAsterisk() == true) it.dropLastWhile { it == ' ' } else it - } - } else { - /* - Outside of the <pre> we would like to trim everything from the start and end of a line since - javadoc doesn't care about it. - */ - text.let { - if ((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true && text.isNotBlank() && previousElement !is PsiInlineDocTag) it?.trimStart() else it - }?.let { - if ((nextSibling as? PsiDocToken)?.isLeadingAsterisk() == true && text.isNotBlank()) it.trimEnd() else it - }?.let { - if (shouldHaveSpaceAtTheEnd()) "$it " else it - } - } - - /** - * We would like to know if we need to have a space after a this tag - * - * The space is required when: - * - tag spans multiple lines, between every line we would need a space - * - * We wouldn't like to render a space if: - * - tag is followed by an end of comment - * - after a tag there is another tag (eg. multiple @author tags) - * - they end with an html tag like: <a href="...">Something</a> since then the space will be displayed in the following text - * - next line starts with a <p> or <pre> token - */ - private fun PsiElement.shouldHaveSpaceAtTheEnd(): Boolean { - val siblings = siblings(withItself = false).toList().filterNot { it.text.trim() == "" } - val nextNotEmptySibling = (siblings.firstOrNull() as? PsiDocToken) - val furtherNotEmptySibling = - (siblings.drop(1).firstOrNull { it is PsiDocToken && !it.isLeadingAsterisk() } as? PsiDocToken) - val lastHtmlTag = text.trim().substringAfterLast("<") - val endsWithAnUnclosedTag = lastHtmlTag.endsWith(">") && !lastHtmlTag.startsWith("</") - - return (nextSibling as? PsiWhiteSpace)?.text?.startsWith("\n ") == true && - (getNextSiblingIgnoringWhitespace() as? PsiDocToken)?.tokenType != JavaDocTokenTypes.INSTANCE.commentEnd() && - nextNotEmptySibling?.isLeadingAsterisk() == true && - furtherNotEmptySibling?.tokenType == JavaDocTokenTypes.INSTANCE.commentData() && - !endsWithAnUnclosedTag - } - - private fun PsiElement.toDocumentationLinkString( - label: String = "" - ): String { - - val dri = reference?.resolve()?.takeIf { it !is PsiParameter }?.let { - val dri = DRI.from(it) - driMap[dri.toString()] = dri - dri.toString() - } ?: UNRESOLVED_PSI_ELEMENT - - return """<a data-dri="${dri.htmlEscape()}">${label.ifBlank{ defaultLabel().text }}</a>""" - } - - private fun convertInlineDocTag( - tag: PsiInlineDocTag, - javadocTag: JavadocTag?, - context: CommentResolutionContext - ) = - when (tag.name) { - "link", "linkplain" -> tag.referenceElement() - ?.toDocumentationLinkString(tag.dataElements.filterIsInstance<PsiDocToken>().joinToString(" ") { - it.stringifyElementAsText(keepFormatting = false).orEmpty() - }) - "code" -> "<code data-inline>${dataElementsAsText(tag)}</code>" - "literal" -> "<literal>${dataElementsAsText(tag)}</literal>" - "index" -> "<index>${tag.children.filterIsInstance<PsiDocTagValue>().joinToString { it.text }}</index>" - "inheritDoc" -> inheritDocResolver.resolveFromContext(context) - ?.fold(ParsingResult(javadocTag)) { result, e -> - result + e.stringify(result.newState, context) - }?.parsedLine.orEmpty() - else -> tag.text - } - - private fun dataElementsAsText(tag: PsiInlineDocTag) = - tag.dataElements.joinToString("") { - it.stringifyElementAsText(keepFormatting = true).orEmpty() - }.htmlEscape() - - private fun createLink(element: Element, children: List<DocTag>): DocTag { - return when { - element.hasAttr("docref") -> - A(children, params = mapOf("docref" to element.attr("docref"))) - element.hasAttr("href") -> - A(children, params = mapOf("href" to element.attr("href"))) - element.hasAttr("data-dri") && driMap.containsKey(element.attr("data-dri")) -> - DocumentationLink(driMap[element.attr("data-dri")]!!, children) - else -> Text(body = children.filterIsInstance<Text>().joinToString { it.body }) - } - } - - private fun createBlock(element: Element, keepFormatting: Boolean = false): List<DocTag> { - val tagName = element.tagName() - val children = element.childNodes() - .flatMap { convertHtmlNode(it, keepFormatting = keepFormatting || tagName == "pre" || tagName == "code") } - - fun ifChildrenPresent(operation: () -> DocTag): List<DocTag> { - return if (children.isNotEmpty()) listOf(operation()) else emptyList() - } - return when (tagName) { - "blockquote" -> ifChildrenPresent { BlockQuote(children) } - "p" -> ifChildrenPresent { P(children) } - "b" -> ifChildrenPresent { B(children) } - "strong" -> ifChildrenPresent { Strong(children) } - "index" -> listOf(Index(children)) - "i" -> ifChildrenPresent { I(children) } - "img" -> listOf( - Img( - children, - element.attributes().associate { (if (it.key == "src") "href" else it.key) to it.value }) - ) - "em" -> listOf(Em(children)) - "code" -> ifChildrenPresent { if(keepFormatting) CodeBlock(children) else CodeInline(children) } - "pre" -> if(children.size == 1) { - when(children.first()) { - is CodeInline -> listOf(CodeBlock(children.first().children)) - is CodeBlock -> listOf(children.first()) - else -> listOf(Pre(children)) - } - } else { - listOf(Pre(children)) - } - "ul" -> ifChildrenPresent { Ul(children) } - "ol" -> ifChildrenPresent { Ol(children) } - "li" -> listOf(Li(children)) - "dl" -> ifChildrenPresent { Dl(children) } - "dt" -> listOf(Dt(children)) - "dd" -> listOf(Dd(children)) - "a" -> listOf(createLink(element, children)) - "table" -> ifChildrenPresent { Table(children) } - "tr" -> ifChildrenPresent { Tr(children) } - "td" -> listOf(Td(children)) - "thead" -> listOf(THead(children)) - "tbody" -> listOf(TBody(children)) - "tfoot" -> listOf(TFoot(children)) - "caption" -> ifChildrenPresent { Caption(children) } - "inheritdoc" -> { - val id = UUID.fromString(element.attr("id")) - val section = inheritDocSections[id] - val parsed = section?.children?.flatMap { it.root.children }.orEmpty() - if(parsed.size == 1 && parsed.first() is P){ - parsed.first().children - } else { - parsed - } - } - "h1" -> ifChildrenPresent { H1(children) } - "h2" -> ifChildrenPresent { H2(children) } - "h3" -> ifChildrenPresent { H3(children) } - "var" -> ifChildrenPresent { Var(children) } - "u" -> ifChildrenPresent { U(children) } - else -> listOf(Text(body = element.ownText())) - } - } - - private fun convertHtmlNode(node: Node, keepFormatting: Boolean = false): List<DocTag> = when (node) { - is TextNode -> (if (keepFormatting) { - node.wholeText.takeIf { it.isNotBlank() }?.let { listOf(Text(body = it)) } - } else { - node.wholeText.parseHtmlEncodedWithNormalisedSpaces(renderWhiteCharactersAsSpaces = true) - }).orEmpty() - is Comment -> listOf(Text(body = node.outerHtml(), params = DocTag.contentTypeParam("html"))) - is Element -> createBlock(node, keepFormatting) - else -> emptyList() - } - - override fun invoke( - elements: Iterable<PsiElement>, - asParagraph: Boolean, - context: CommentResolutionContext - ): List<DocTag> = - elements.fold(ParsingResult(context.tag)) { acc, e -> - acc + e.stringify(acc.newState, context) - }.parsedLine?.let { - val trimmed = it.trim() - val toParse = if (asParagraph) "<p>$trimmed</p>" else trimmed - Jsoup.parseBodyFragment(toParse).body().childNodes().flatMap { convertHtmlNode(it) } - }.orEmpty() - } - - private fun convertJavadocElements( - elements: Iterable<PsiElement>, - asParagraph: Boolean = true, - context: CommentResolutionContext - ): List<DocTag> = - Parse()(elements, asParagraph, context) - - private fun PsiDocToken.isSharpToken() = tokenType == JavaDocTokenType.DOC_TAG_VALUE_SHARP_TOKEN - - private fun PsiDocToken.isTagName() = tokenType == JavaDocTokenType.DOC_TAG_NAME - - private fun PsiDocToken.isLeadingAsterisk() = tokenType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS - - private fun PsiElement.toDocumentationLink(labelElement: PsiElement? = null, context: CommentResolutionContext) = - resolveToGetDri()?.let { - val dri = DRI.from(it) - val label = labelElement ?: defaultLabel() - DocumentationLink(dri, convertJavadocElements(listOfNotNull(label), asParagraph = false, context)) - } - - private fun PsiDocTag.referenceElement(): PsiElement? = - linkElement()?.referenceElementOrSelf() - - private fun PsiElement.defaultLabel() = children.firstOrNull { - it is PsiDocToken && it.text.isNotBlank() && !it.isSharpToken() - } ?: this - - private fun PsiDocTag.linkElement(): PsiElement? = - valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace } - - companion object { - private const val UNRESOLVED_PSI_ELEMENT = "UNRESOLVED_PSI_ELEMENT" - } -} diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocTag.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocTag.kt index 5b3be7e3..e69de29b 100644 --- a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocTag.kt +++ b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocTag.kt @@ -1,32 +0,0 @@ -package org.jetbrains.dokka.base.translators.psi.parsers - -internal enum class JavadocTag { - PARAM, THROWS, RETURN, AUTHOR, SEE, DEPRECATED, EXCEPTION, HIDE, SINCE, - - /** - * Artificial tag created to handle tag-less section - */ - DESCRIPTION,; - - override fun toString(): String = super.toString().toLowerCase() - - /* Missing tags: - SERIAL, - SERIAL_DATA, - SERIAL_FIELD, - SINCE, - VERSION - */ - - companion object { - private val name2Value = values().associateBy { it.name.toLowerCase() } - - /** - * Lowercase-based `Enum.valueOf` variation for [JavadocTag]. - * - * Note: tags are [case-sensitive](https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html) in Java, - * thus we are not allowed to use case-insensitive or uppercase-based lookup. - */ - fun lowercaseValueOfOrNull(name: String): JavadocTag? = name2Value[name] - } -} diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt deleted file mode 100644 index c4c8cbb2..00000000 --- a/plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt +++ /dev/null @@ -1,146 +0,0 @@ -package org.jetbrains.dokka.base.translators.psi.parsers - -import com.intellij.psi.* -import com.intellij.psi.javadoc.PsiDocComment -import com.intellij.psi.javadoc.PsiDocTag -import org.jetbrains.dokka.analysis.from -import org.jetbrains.dokka.base.translators.psi.findSuperMethodsOrEmptyArray -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.idea.kdoc.findKDoc -import org.jetbrains.kotlin.idea.base.utils.fqname.getKotlinFqName -import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor -import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag -import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull - -internal interface DocComment { - fun hasTag(tag: JavadocTag): Boolean - fun hasTagWithExceptionOfType(tag: JavadocTag, exceptionFqName: String): Boolean - fun tagsByName(tag: JavadocTag, param: String? = null): List<DocumentationContent> -} - -internal data class JavaDocComment(val comment: PsiDocComment) : DocComment { - override fun hasTag(tag: JavadocTag): Boolean = comment.hasTag(tag) - override fun hasTagWithExceptionOfType(tag: JavadocTag, exceptionFqName: String): Boolean = - comment.hasTag(tag) && comment.tagsByName(tag).firstIsInstanceOrNull<PsiDocTag>() - ?.resolveToElement() - ?.getKotlinFqName()?.asString() == exceptionFqName - - override fun tagsByName(tag: JavadocTag, param: String?): List<DocumentationContent> = - comment.tagsByName(tag).map { PsiDocumentationContent(it, tag) } -} - -internal data class KotlinDocComment(val comment: KDocTag, val descriptor: DeclarationDescriptor) : DocComment { - override fun hasTag(tag: JavadocTag): Boolean = - when (tag) { - JavadocTag.DESCRIPTION -> comment.getContent().isNotEmpty() - else -> tagsWithContent.any { it.text.startsWith("@$tag") } - } - - override fun hasTagWithExceptionOfType(tag: JavadocTag, exceptionFqName: String): Boolean = - tagsWithContent.any { it.hasExceptionWithName(tag, exceptionFqName) } - - override fun tagsByName(tag: JavadocTag, param: String?): List<DocumentationContent> = - when (tag) { - JavadocTag.DESCRIPTION -> listOf(DescriptorDocumentationContent(descriptor, comment, tag)) - else -> comment.children.mapNotNull { (it as? KDocTag) } - .filter { it.name == "$tag" && param?.let { param -> it.hasExceptionWithName(param) } != false } - .map { DescriptorDocumentationContent(descriptor, it, tag) } - } - - private val tagsWithContent: List<KDocTag> = comment.children.mapNotNull { (it as? KDocTag) } - - private fun KDocTag.hasExceptionWithName(tag: JavadocTag, exceptionFqName: String) = - text.startsWith("@$tag") && hasExceptionWithName(exceptionFqName) - - private fun KDocTag.hasExceptionWithName(exceptionFqName: String) = - getSubjectName() == exceptionFqName -} - -internal interface DocumentationContent { - val tag: JavadocTag -} - -internal data class PsiDocumentationContent(val psiElement: PsiElement, override val tag: JavadocTag) : - DocumentationContent - -internal data class DescriptorDocumentationContent( - val descriptor: DeclarationDescriptor, - val element: KDocTag, - override val tag: JavadocTag -) : DocumentationContent - -internal fun PsiDocComment.hasTag(tag: JavadocTag): Boolean = - when (tag) { - JavadocTag.DESCRIPTION -> descriptionElements.isNotEmpty() - else -> findTagByName(tag.toString()) != null - } - -internal fun PsiDocComment.tagsByName(tag: JavadocTag): List<PsiElement> = - when (tag) { - JavadocTag.DESCRIPTION -> descriptionElements.toList() - else -> findTagsByName(tag.toString()).toList() - } - -internal fun findClosestDocComment(element: PsiNamedElement, logger: DokkaLogger): DocComment? { - (element as? PsiDocCommentOwner)?.docComment?.run { return JavaDocComment(this) } - element.toKdocComment()?.run { return this } - - if (element is PsiMethod) { - val superMethods = element.findSuperMethodsOrEmptyArray(logger) - if (superMethods.isEmpty()) return null - - if (superMethods.size == 1) { - return findClosestDocComment(superMethods.single(), logger) - } - - val superMethodDocumentation = superMethods.map { method -> findClosestDocComment(method, logger) }.distinct() - if (superMethodDocumentation.size == 1) { - return superMethodDocumentation.single() - } - - logger.debug( - "Conflicting documentation for ${DRI.from(element)}" + - "${superMethods.map { DRI.from(it) }}" - ) - - /* Prioritize super class over interface */ - val indexOfSuperClass = superMethods.indexOfFirst { method -> - val parent = method.parent - if (parent is PsiClass) !parent.isInterface - else false - } - - return if (indexOfSuperClass >= 0) superMethodDocumentation[indexOfSuperClass] - else superMethodDocumentation.first() - } - return element.children.firstIsInstanceOrNull<PsiDocComment>()?.let { JavaDocComment(it) } -} - -internal fun PsiNamedElement.toKdocComment(): KotlinDocComment? = - (navigationElement as? KtElement)?.findKDoc { DescriptorToSourceUtils.descriptorToDeclaration(it) } - ?.run { - (this@toKdocComment.navigationElement as? KtDeclaration)?.descriptor?.let { - KotlinDocComment( - this, - it - ) - } - } - -internal fun PsiDocTag.contentElementsWithSiblingIfNeeded(): List<PsiElement> = if (dataElements.isNotEmpty()) { - listOfNotNull( - dataElements[0], - dataElements[0].nextSibling?.takeIf { it.text != dataElements.drop(1).firstOrNull()?.text }, - *dataElements.drop(1).toTypedArray() - ) -} else { - emptyList() -} - -internal fun PsiDocTag.resolveToElement(): PsiElement? = - dataElements.firstOrNull()?.firstChild?.referenceElementOrSelf()?.resolveToGetDri() diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/exceptionTag.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/exceptionTag.kt deleted file mode 100644 index 3cc16251..00000000 --- a/plugins/base/src/main/kotlin/translators/psi/parsers/exceptionTag.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.jetbrains.dokka.base.translators.psi.parsers - -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiJavaCodeReferenceElement -import com.intellij.psi.impl.source.tree.JavaDocElementType -import com.intellij.psi.util.PsiTreeUtil - -internal fun PsiElement.referenceElementOrSelf(): PsiElement? = - if (node.elementType == JavaDocElementType.DOC_REFERENCE_HOLDER) { - PsiTreeUtil.findChildOfType(this, PsiJavaCodeReferenceElement::class.java) - } else this - -internal fun PsiElement.resolveToGetDri(): PsiElement? = - reference?.resolve()
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/CollectionExtensions.kt b/plugins/base/src/main/kotlin/utils/CollectionExtensions.kt index 0de4b5b1..6fc5271f 100644 --- a/plugins/base/src/main/kotlin/translators/CollectionExtensions.kt +++ b/plugins/base/src/main/kotlin/utils/CollectionExtensions.kt @@ -1,4 +1,4 @@ -package org.jetbrains.dokka.base.translators +package org.jetbrains.dokka.base.utils // TODO [beresnev] remove this copy-paste and use the same method from stdlib instead after updating to 1.5 internal inline fun <T, R : Any> Iterable<T>.firstNotNullOfOrNull(transform: (T) -> R?): R? { diff --git a/plugins/base/src/main/kotlin/utils/NoopIntellijLogger.kt b/plugins/base/src/main/kotlin/utils/NoopIntellijLogger.kt deleted file mode 100644 index 9248f996..00000000 --- a/plugins/base/src/main/kotlin/utils/NoopIntellijLogger.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.jetbrains.dokka.base.utils - -import com.intellij.openapi.diagnostic.Attachment -import com.intellij.openapi.diagnostic.DefaultLogger -import com.intellij.openapi.diagnostic.Logger - -internal class NoopIntellijLoggerFactory : Logger.Factory { - override fun getLoggerInstance(p0: String): Logger = NoopIntellijLogger -} - -/** - * Ignores all messages passed to it - */ -internal object NoopIntellijLogger : DefaultLogger(null) { - override fun isDebugEnabled(): Boolean = false - override fun isTraceEnabled(): Boolean = false - - override fun debug(message: String?) {} - override fun debug(t: Throwable?) {} - override fun debug(message: String?, t: Throwable?) {} - override fun debug(message: String, vararg details: Any?) {} - override fun debugValues(header: String, values: MutableCollection<*>) {} - - override fun trace(message: String?) {} - override fun trace(t: Throwable?) {} - - override fun info(message: String?) {} - override fun info(message: String?, t: Throwable?) {} - override fun info(t: Throwable) {} - - override fun warn(message: String?, t: Throwable?) {} - override fun warn(message: String?) {} - override fun warn(t: Throwable) {} - - override fun error(message: String?, t: Throwable?, vararg details: String?) {} - override fun error(message: String?) {} - override fun error(message: Any?) {} - override fun error(message: String?, vararg attachments: Attachment?) {} - override fun error(message: String?, t: Throwable?, vararg attachments: Attachment?) {} - override fun error(message: String?, vararg details: String?) {} - override fun error(message: String?, t: Throwable?) {} - override fun error(t: Throwable) {} -} |