diff options
author | Ignat Beresnev <ignat.beresnev@jetbrains.com> | 2023-07-05 10:04:55 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-05 10:04:55 +0200 |
commit | 9559158bfeeb274e9ccf1b4563f1b23b42afc493 (patch) | |
tree | 3ece0887623cfe2b7148af23001867a1dd5e6597 /plugins/base/src/main/kotlin/translators/psi/parsers | |
parent | cbd9733d3dd2f52992e98e7cebd072091a572529 (diff) | |
download | dokka-9559158bfeeb274e9ccf1b4563f1b23b42afc493.tar.gz dokka-9559158bfeeb274e9ccf1b4563f1b23b42afc493.tar.bz2 dokka-9559158bfeeb274e9ccf1b4563f1b23b42afc493.zip |
Decompose Kotlin/Java analysis (#3034)
* Extract analysis into separate modules
Diffstat (limited to 'plugins/base/src/main/kotlin/translators/psi/parsers')
5 files changed, 0 insertions, 832 deletions
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 |