aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main
diff options
context:
space:
mode:
authorMarcin Aman <marcin.aman@gmail.com>2020-11-12 12:01:00 +0100
committerGitHub <noreply@github.com>2020-11-12 12:01:00 +0100
commit6a1c05c2d340a6812a8b58d3027d8e5712db45a2 (patch)
treeb14c5b0a26fbb61bb5492b1a778e5df57fcd584d /plugins/base/src/main
parent7db15c357a417ccd9ff8ad1f90f5aff84eec132f (diff)
downloaddokka-6a1c05c2d340a6812a8b58d3027d8e5712db45a2.tar.gz
dokka-6a1c05c2d340a6812a8b58d3027d8e5712db45a2.tar.bz2
dokka-6a1c05c2d340a6812a8b58d3027d8e5712db45a2.zip
Javadoc @inheritDoc tag support (#1608)
Diffstat (limited to 'plugins/base/src/main')
-rw-r--r--plugins/base/src/main/kotlin/parsers/MarkdownParser.kt20
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt2
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/PsiInheritance.kt47
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/parsers/InheritDocResolver.kt106
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt (renamed from plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt)302
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/parsers/JavadocTag.kt20
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt68
-rw-r--r--plugins/base/src/main/kotlin/translators/psi/parsers/exceptionTag.kt15
8 files changed, 414 insertions, 166 deletions
diff --git a/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt b/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt
index f496d704..0b9a5c23 100644
--- a/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt
+++ b/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt
@@ -42,10 +42,11 @@ open class MarkdownParser(
when (tagName) {
"see" -> {
val referencedName = content.substringBefore(' ')
+ val dri = externalDri(referencedName)
See(
parseStringToDocNode(content.substringAfter(' ')),
- referencedName,
- externalDri(referencedName)
+ dri?.fqName() ?: referencedName,
+ dri
)
}
"throws", "exception" -> {
@@ -481,11 +482,14 @@ open class MarkdownParser(
)
KDocKnownTag.RECEIVER -> Receiver(parseStringToDocNode(it.getContent()))
KDocKnownTag.RETURN -> Return(parseStringToDocNode(it.getContent()))
- KDocKnownTag.SEE -> See(
- parseStringToDocNode(it.getContent()),
- it.getSubjectName().orEmpty(),
- pointedLink(it),
- )
+ KDocKnownTag.SEE -> {
+ val dri = pointedLink(it)
+ See(
+ parseStringToDocNode(it.getContent()),
+ dri?.fqName() ?: it.getSubjectName().orEmpty(),
+ dri,
+ )
+ }
KDocKnownTag.SINCE -> Since(parseStringToDocNode(it.getContent()))
KDocKnownTag.CONSTRUCTOR -> Constructor(parseStringToDocNode(it.getContent()))
KDocKnownTag.PROPERTY -> Property(
@@ -504,7 +508,7 @@ open class MarkdownParser(
}
//Horrible hack but since link resolution is passed as a function i am not able to resolve them otherwise
- fun DRI.fqName(): String = "$packageName.$classNames"
+ fun DRI.fqName(): String? = "$packageName.$classNames".takeIf { packageName != null && classNames != null }
private fun findParent(kDoc: PsiElement): PsiElement =
if (kDoc is KDocSection) findParent(kDoc.parent) else kDoc
diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt
index 1f52f373..15ef24bb 100644
--- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt
+++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt
@@ -15,6 +15,8 @@ import org.jetbrains.dokka.analysis.PsiDocumentableSource
import org.jetbrains.dokka.analysis.from
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.translators.isDirectlyAnException
+import org.jetbrains.dokka.base.translators.psi.parsers.JavaDocumentationParser
+import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.links.nextTarget
import org.jetbrains.dokka.links.withClass
diff --git a/plugins/base/src/main/kotlin/translators/psi/PsiInheritance.kt b/plugins/base/src/main/kotlin/translators/psi/PsiInheritance.kt
new file mode 100644
index 00000000..dcfbb8e3
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/psi/PsiInheritance.kt
@@ -0,0 +1,47 @@
+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.refactoring.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/parsers/InheritDocResolver.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/InheritDocResolver.kt
new file mode 100644
index 00000000..c2fb6fb4
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/psi/parsers/InheritDocResolver.kt
@@ -0,0 +1,106 @@
+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
+import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
+
+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 -> resolveGenericTag(context.comment, context.tag)
+ }
+
+ private fun resolveGenericTag(currentElement: PsiDocComment, tag: JavadocTag): List<PsiElement> =
+ when (val owner = currentElement.owner) {
+ is PsiClass -> lowestClassWithTag(owner, tag)
+ is PsiMethod -> lowestMethodWithTag(owner, tag)
+ else -> null
+ }?.tagsByName(tag)?.flatMap {
+ when (it) {
+ is PsiDocTag -> it.contentElementsWithSiblingIfNeeded()
+ 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<PsiElement> =
+ (currentElement.owner as? PsiMethod)?.let { method -> lowestMethodsWithTag(method, tag) }
+ .orEmpty().firstOrNull {
+ findClosestDocComment(it, logger)?.hasTagWithExceptionOfType(tag, exceptionFqName) == true
+ }?.docComment?.tagsByName(tag)?.flatMap {
+ when (it) {
+ is PsiDocTag -> it.contentElementsWithSiblingIfNeeded()
+ else -> listOf(it)
+ }
+ }?.withoutReferenceLink().orEmpty()
+
+ private fun resolveParamTag(
+ currentElement: PsiDocComment,
+ parameterIndex: Int,
+ ): List<PsiElement> =
+ (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)
+ if (hasTag != true) emptyList()
+ else {
+ val parameterName = it.parameterList.parameters[parameterIndex].name
+ closestTag.tagsByName(JavadocTag.PARAM)
+ .filterIsInstance<PsiDocTag>().map { it.contentElementsWithSiblingIfNeeded() }.firstOrNull {
+ it.firstOrNull()?.text == parameterName
+ }.orEmpty()
+ }
+ }
+ }.withoutReferenceLink()
+
+ //if we are in psi class javadoc only inherits docs from classes and not from interfaces
+ private fun lowestClassWithTag(baseClass: PsiClass, javadocTag: JavadocTag): PsiDocComment? =
+ baseClass.superClass?.let {
+ findClosestDocComment(it, logger)?.takeIf { tag -> tag.hasTag(javadocTag) } ?:
+ lowestClassWithTag(it, javadocTag)
+ }
+
+ private fun lowestMethodWithTag(
+ baseMethod: PsiMethod,
+ javadocTag: JavadocTag,
+ ): PsiDocComment? =
+ lowestMethodsWithTag(baseMethod, javadocTag).firstOrNull()?.docComment
+
+ private fun lowestMethodsWithTag(baseMethod: PsiMethod, javadocTag: JavadocTag) =
+ baseMethod.findSuperMethods().filter { findClosestDocComment(it, logger)?.hasTag(javadocTag) == true }
+
+ private fun PsiDocComment.hasTagWithExceptionOfType(tag: JavadocTag, exceptionFqName: String): Boolean =
+ hasTag(tag) && tagsByName(tag).firstIsInstanceOrNull<PsiDocTag>()
+ ?.resolveToElement()
+ ?.getKotlinFqName()?.asString() == exceptionFqName
+
+ private fun List<PsiElement>.withoutReferenceLink(): List<PsiElement> = drop(1)
+} \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt
index 782f792a..96c62b36 100644
--- a/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt
+++ b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt
@@ -1,25 +1,23 @@
-package org.jetbrains.dokka.base.translators.psi
+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 com.intellij.psi.util.PsiTreeUtil
import org.intellij.markdown.MarkdownElementTypes
import org.jetbrains.dokka.analysis.from
-import org.jetbrains.dokka.base.parsers.factories.DocTagsFromStringFactory
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.enumValueOrNull
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
-import org.jetbrains.kotlin.name.FqName
+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.jetbrains.kotlin.tools.projectWizard.core.ParsingState
-import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
-import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.nodes.Node
@@ -30,53 +28,97 @@ interface JavaDocumentationParser {
}
class JavadocParser(
- private val logger: DokkaLogger // TODO: Add logging
+ private val logger: DokkaLogger
) : JavaDocumentationParser {
+ private val inheritDocResolver = InheritDocResolver(logger)
override fun parseDocumentation(element: PsiNamedElement): DocumentationNode {
- val docComment = findClosestDocComment(element) ?: return DocumentationNode(emptyList())
- val nodes = mutableListOf<TagWrapper>()
- docComment.getDescription()?.let { nodes.add(it) }
- nodes.addAll(docComment.tags.mapNotNull { tag ->
- when (tag.name) {
- "param" -> Param(
- wrapTagIfNecessary(convertJavadocElements(tag.contentElementsWithSiblingIfNeeded().drop(1))),
- tag.dataElements.firstOrNull()?.text.orEmpty()
- )
- "throws" -> {
- val resolved = tag.resolveException()
+ val docComment = findClosestDocComment(element, logger) ?: return DocumentationNode(emptyList())
+ val nodes = listOfNotNull(docComment.getDescription()) + docComment.tags.mapNotNull { tag ->
+ parseDocTag(tag, docComment, element)
+ }
+ return DocumentationNode(nodes)
+ }
+
+ private fun parseDocTag(tag: PsiDocTag, docComment: PsiDocComment, analysedElement: PsiNamedElement): TagWrapper? =
+ enumValueOrNull<JavadocTag>(tag.name)?.let { javadocTag ->
+ val resolutionContext = CommentResolutionContext(comment = docComment, tag = javadocTag)
+ 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))),
+ 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 = resolved?.getKotlinFqName()?.asString()
- ?: tag.dataElements.firstOrNull()?.text.orEmpty(),
+ name = name,
exceptionAddress = dri
)
}
- "return" -> Return(wrapTagIfNecessary(convertJavadocElements(tag.contentElementsWithSiblingIfNeeded())))
- "author" -> Author(wrapTagIfNecessary(convertJavadocElements(tag.contentElementsWithSiblingIfNeeded()))) // 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
- "see" -> getSeeTagElementContent(tag).let {
- See(
- wrapTagIfNecessary(it.first),
- tag.referenceElement()?.text.orEmpty(),
- it.second
+ 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()
+ getSeeTagElementContent(tag, resolutionContext.copy(name = name)).let {
+ See(
+ wrapTagIfNecessary(it.first),
+ name,
+ it.second
+ )
+ }
}
- "deprecated" -> Deprecated(wrapTagIfNecessary(convertJavadocElements(tag.contentElementsWithSiblingIfNeeded())))
+ JavadocTag.DEPRECATED -> Deprecated(
+ wrapTagIfNecessary(
+ convertJavadocElements(
+ tag.contentElementsWithSiblingIfNeeded(),
+ context = resolutionContext
+ )
+ )
+ )
else -> null
+ //TODO https://github.com/Kotlin/dokka/issues/1618
}
- })
- return DocumentationNode(nodes)
- }
-
- private fun PsiDocTag.resolveException(): PsiElement? =
- dataElements.firstOrNull()?.firstChild?.referenceElementOrSelf()?.resolveToGetDri()
+ }
private fun wrapTagIfNecessary(list: List<DocTag>): CustomDocTag =
if (list.size == 1 && (list.first() as? CustomDocTag)?.name == MarkdownElementTypes.MARKDOWN_FILE.name)
@@ -84,96 +126,37 @@ class JavadocParser(
else
CustomDocTag(list, name = MarkdownElementTypes.MARKDOWN_FILE.name)
- private fun findClosestDocComment(element: PsiNamedElement): PsiDocComment? {
- (element as? PsiDocCommentOwner)?.docComment?.run { return this }
- if (element is PsiMethod) {
- val superMethods = element.findSuperMethodsOrEmptyArray()
- if (superMethods.isEmpty()) return null
-
- if (superMethods.size == 1) {
- return findClosestDocComment(superMethods.single())
- }
-
- val superMethodDocumentation = superMethods.map(::findClosestDocComment)
- if (superMethodDocumentation.size == 1) {
- return superMethodDocumentation.single()
- }
-
- logger.warn(
- "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>()
- }
-
- /**
- * 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)
- */
- private fun PsiMethod.findSuperMethodsOrEmptyArray(): 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()
- }
- }
-
- private fun PsiClass.implementsInterface(fqName: FqName): Boolean {
- return allInterfaces().any { it.getKotlinFqName() == fqName }
- }
-
- private fun PsiClass.allInterfaces(): Sequence<PsiClass> {
- return sequence {
- this.yieldAll(interfaces.toList())
- interfaces.forEach { yieldAll(it.allInterfaces()) }
- }
- }
-
- private fun getSeeTagElementContent(tag: PsiDocTag): Pair<List<DocumentationLink>, DRI?> {
- val content = tag.referenceElement()?.toDocumentationLink()
- return Pair(listOfNotNull(content), content?.dri)
+ private fun getSeeTagElementContent(
+ tag: PsiDocTag,
+ context: CommentResolutionContext
+ ): Pair<List<DocTag>, DRI?> {
+ val linkElement = tag.referenceElement()?.toDocumentationLink(context = context)
+ val content = convertJavadocElements(
+ tag.dataElements.dropWhile { it is PsiWhiteSpace || (it as? LazyParseablePsiElement)?.tokenType == JavaDocElementType.DOC_REFERENCE_HOLDER },
+ context = context
+ )
+ return Pair(content, linkElement?.dri)
}
private fun PsiDocComment.getDescription(): Description? {
- return convertJavadocElements(descriptionElements.asIterable()).takeIf { it.isNotEmpty() }?.let {
+ 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 = ParserState(), val parsedLine: String? = null) {
+ 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,
@@ -181,21 +164,26 @@ class JavadocParser(
)
}
- private inner class Parse : (Iterable<PsiElement>, Boolean) -> List<DocTag> {
+ private inner class Parse : (Iterable<PsiElement>, Boolean, CommentResolutionContext) -> List<DocTag> {
val driMap = mutableMapOf<String, DRI>()
- private fun PsiElement.stringify(state: ParserState): ParsingResult =
+ private fun PsiElement.stringify(state: ParserState, context: CommentResolutionContext): ParsingResult =
when (this) {
- is PsiReference -> children.fold(ParsingResult(state)) { acc, e -> acc + e.stringify(acc.newState) }
- else -> stringifySimpleElement(state)
+ is PsiReference -> children.fold(ParsingResult(state)) { acc, e ->
+ acc + e.stringify(acc.newState, context)
+ }
+ else -> stringifySimpleElement(state, context)
}
- private fun PsiElement.stringifySimpleElement(state: ParserState): ParsingResult {
+ 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)
+ is PsiInlineDocTag -> convertInlineDocTag(this, state.currentJavadocTag, context)
is PsiDocParamRef -> toDocumentationLinkString()
is PsiDocTagValue,
is LeafPsiElement -> {
@@ -205,7 +193,8 @@ class JavadocParser(
since it is there because it separates this line from the leading asterisk
*/
text.let {
- if ((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true && it.firstOrNull() == ' ') it.drop(1) else it
+ if ((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true && it.firstOrNull() == ' ')
+ it.drop(1) else it
}.let {
if ((nextSibling as? PsiDocToken)?.isLeadingAsterisk() == true) it.dropLastWhile { it == ' ' } else it
}
@@ -256,9 +245,9 @@ class JavadocParser(
val endsWithAnUnclosedTag = lastHtmlTag.endsWith(">") && !lastHtmlTag.startsWith("</")
return (nextSibling as? PsiWhiteSpace)?.text == "\n " &&
- (getNextSiblingIgnoringWhitespace() as? PsiDocToken)?.tokenType?.toString() != END_COMMENT_TYPE &&
+ (getNextSiblingIgnoringWhitespace() as? PsiDocToken)?.tokenType != JavaDocTokenTypes.INSTANCE.commentEnd() &&
nextNotEmptySibling?.isLeadingAsterisk() == true &&
- furtherNotEmptySibling?.tokenType?.toString() == COMMENT_TYPE &&
+ furtherNotEmptySibling?.tokenType == JavaDocTokenTypes.INSTANCE.commentData() &&
!endsWithAnUnclosedTag
}
@@ -276,13 +265,22 @@ class JavadocParser(
return """<a data-dri="$dri">${label.joinToString(" ") { it.text }}</a>"""
}
- private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) {
- "link", "linkplain" -> tag.referenceElement()
- ?.toDocumentationLinkString(tag.dataElements.filterIsInstance<PsiDocToken>())
- "code", "literal" -> "<code data-inline>${tag.text}</code>"
- "index" -> "<index>${tag.children.filterIsInstance<PsiDocTagValue>().joinToString { it.text }}</index>"
- else -> tag.text
- }
+ private fun convertInlineDocTag(
+ tag: PsiInlineDocTag,
+ javadocTag: JavadocTag,
+ context: CommentResolutionContext
+ ) =
+ when (tag.name) {
+ "link", "linkplain" -> tag.referenceElement()
+ ?.toDocumentationLinkString(tag.dataElements.filterIsInstance<PsiDocToken>())
+ "code", "literal" -> "<code data-inline>${tag.text}</code>"
+ "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 createLink(element: Element, children: List<DocTag>): DocTag {
return when {
@@ -335,9 +333,13 @@ class JavadocParser(
else -> null
}
- override fun invoke(elements: Iterable<PsiElement>, asParagraph: Boolean): List<DocTag> =
- elements.fold(ParsingResult()) { acc, e ->
- acc + e.stringify(acc.newState)
+ 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
@@ -345,41 +347,27 @@ class JavadocParser(
}.orEmpty()
}
- private 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()
- }
-
- private fun convertJavadocElements(elements: Iterable<PsiElement>, asParagraph: Boolean = true): List<DocTag> =
- Parse()(elements, asParagraph)
+ private fun convertJavadocElements(
+ elements: Iterable<PsiElement>,
+ asParagraph: Boolean = true,
+ context: CommentResolutionContext
+ ): List<DocTag> =
+ Parse()(elements, asParagraph, context)
- private fun PsiDocToken.isSharpToken() = tokenType.toString() == "DOC_TAG_VALUE_SHARP_TOKEN"
+ private fun PsiDocToken.isSharpToken() = tokenType == JavaDocTokenType.DOC_TAG_VALUE_SHARP_TOKEN
- private fun PsiDocToken.isLeadingAsterisk() = tokenType.toString() == "DOC_COMMENT_LEADING_ASTERISKS"
+ private fun PsiDocToken.isLeadingAsterisk() = tokenType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS
- private fun PsiElement.toDocumentationLink(labelElement: PsiElement? = null) =
+ 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))
+ DocumentationLink(dri, convertJavadocElements(listOfNotNull(label), asParagraph = false, context))
}
- private fun PsiElement.resolveToGetDri(): PsiElement? =
- reference?.resolve()
-
private fun PsiDocTag.referenceElement(): PsiElement? =
linkElement()?.referenceElementOrSelf()
- private fun PsiElement.referenceElementOrSelf(): PsiElement? =
- if (node.elementType == JavaDocElementType.DOC_REFERENCE_HOLDER) {
- PsiTreeUtil.findChildOfType(this, PsiJavaCodeReferenceElement::class.java)
- } else this
-
private fun PsiElement.defaultLabel() = children.firstOrNull {
it is PsiDocToken && it.text.isNotBlank() && !it.isSharpToken()
} ?: this
@@ -389,7 +377,5 @@ class JavadocParser(
companion object {
private const val UNRESOLVED_PSI_ELEMENT = "UNRESOLVED_PSI_ELEMENT"
- private const val END_COMMENT_TYPE = "DOC_COMMENT_END"
- private const val COMMENT_TYPE = "DOC_COMMENT_DATA"
}
}
diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocTag.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocTag.kt
new file mode 100644
index 00000000..8ea39453
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocTag.kt
@@ -0,0 +1,20 @@
+package org.jetbrains.dokka.base.translators.psi.parsers
+
+internal enum class JavadocTag {
+ PARAM, THROWS, RETURN, AUTHOR, SEE, DEPRECATED, EXCEPTION,
+
+ /**
+ * 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
+ */
+} \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt
new file mode 100644
index 00000000..80b37052
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt
@@ -0,0 +1,68 @@
+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.utils.addToStdlib.firstIsInstanceOrNull
+
+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): PsiDocComment? {
+ (element as? PsiDocCommentOwner)?.docComment?.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) }
+ if (superMethodDocumentation.size == 1) {
+ return superMethodDocumentation.single()
+ }
+
+ logger.warn(
+ "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>()
+}
+
+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() \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/exceptionTag.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/exceptionTag.kt
new file mode 100644
index 00000000..6e1850bb
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/psi/parsers/exceptionTag.kt
@@ -0,0 +1,15 @@
+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.javadoc.PsiDocTag
+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