diff options
author | Marcin Aman <marcin.aman@gmail.com> | 2020-11-12 12:01:00 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-12 12:01:00 +0100 |
commit | 6a1c05c2d340a6812a8b58d3027d8e5712db45a2 (patch) | |
tree | b14c5b0a26fbb61bb5492b1a778e5df57fcd584d /plugins/base | |
parent | 7db15c357a417ccd9ff8ad1f90f5aff84eec132f (diff) | |
download | dokka-6a1c05c2d340a6812a8b58d3027d8e5712db45a2.tar.gz dokka-6a1c05c2d340a6812a8b58d3027d8e5712db45a2.tar.bz2 dokka-6a1c05c2d340a6812a8b58d3027d8e5712db45a2.zip |
Javadoc @inheritDoc tag support (#1608)
Diffstat (limited to 'plugins/base')
-rw-r--r-- | plugins/base/src/main/kotlin/parsers/MarkdownParser.kt | 20 | ||||
-rw-r--r-- | plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt | 2 | ||||
-rw-r--r-- | plugins/base/src/main/kotlin/translators/psi/PsiInheritance.kt | 47 | ||||
-rw-r--r-- | plugins/base/src/main/kotlin/translators/psi/parsers/InheritDocResolver.kt | 106 | ||||
-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.kt | 20 | ||||
-rw-r--r-- | plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt | 68 | ||||
-rw-r--r-- | plugins/base/src/main/kotlin/translators/psi/parsers/exceptionTag.kt | 15 | ||||
-rw-r--r-- | plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt | 8 | ||||
-rw-r--r-- | plugins/base/src/test/kotlin/translators/JavadocInheritDocsTest.kt | 309 | ||||
-rw-r--r-- | plugins/base/src/test/kotlin/translators/JavadocInheritedDocTagsTest.kt | 247 |
11 files changed, 974 insertions, 170 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 diff --git a/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt b/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt index 4bc33c03..27cdc389 100644 --- a/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt +++ b/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt @@ -213,7 +213,7 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { check { assertEquals("kotlin.collections/Collection///PointingToDeclaration/", (this as ContentDRILink).address.toString()) } - +"Collection" + +"kotlin.collections.Collection" } group { } } @@ -269,7 +269,7 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { table { group { //DRI should be "test//abc/#/-1/" - link { +"Collection" } + link { +"kotlin.collections.Collection" } group { group { +"Comment to stdliblink" } } @@ -333,7 +333,7 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { table { group { //DRI should be "test//abc/#/-1/" - link { +"Collection" } + link { +"kotlin.collections.Collection" } group { group { +"Comment to stdliblink" } } @@ -456,7 +456,7 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { } group { //DRI should be "test//abc/#/-1/" - link { +"Collection" } + link { +"kotlin.collections.Collection" } group { group { +"Comment to collection" } } } } diff --git a/plugins/base/src/test/kotlin/translators/JavadocInheritDocsTest.kt b/plugins/base/src/test/kotlin/translators/JavadocInheritDocsTest.kt new file mode 100644 index 00000000..8fac13c9 --- /dev/null +++ b/plugins/base/src/test/kotlin/translators/JavadocInheritDocsTest.kt @@ -0,0 +1,309 @@ +package translators + +import org.jetbrains.dokka.model.doc.CustomDocTag +import org.jetbrains.dokka.model.doc.Description +import org.jetbrains.dokka.model.doc.P +import org.jetbrains.dokka.model.doc.Text +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.Ignore +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + +class JavadocInheritDocsTest : AbstractCoreTest() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/java") + } + } + } + + @Test + fun `work when whole description is inherited`() { + testInline( + """ + |/src/main/java/sample/Superclass.java + |package sample; + |/** + |* Superclass docs + |*/ + |public class Superclass { } + | + |/src/main/java/sample/Subclass.java + |package sample; + |/** + |* {@inheritDoc} + |*/ + |public class Subclass extends Superclass { } + """.trimIndent(), configuration + ) { + documentablesMergingStage = { module -> + val subclass = module.findClasslike("sample", "Subclass") + val descriptionGot = subclass.documentation.values.first().children.first() + val expectedDescription = Description( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("Superclass docs")) + ) + ), + name = "MARKDOWN_FILE" + ) + ) + + assertEquals(expectedDescription, descriptionGot) + } + } + } + + @Test + fun `work when inherited part is inside description`() { + testInline( + """ + |/src/main/java/sample/Superclass.java + |package sample; + |/** + |* Superclass docs + |*/ + |public class Superclass { } + | + |/src/main/java/sample/Subclass.java + |package sample; + |/** + |* Subclass docs. {@inheritDoc} End of subclass docs + |*/ + |public class Subclass extends Superclass { } + """.trimIndent(), configuration + ) { + documentablesMergingStage = { module -> + val subclass = module.findClasslike("sample", "Subclass") + val descriptionGot = subclass.documentation.values.first().children.first() + val expectedDescription = Description( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("Subclass docs. Superclass docs End of subclass docs")) + ) + ), + name = "MARKDOWN_FILE" + ) + ) + + assertEquals(expectedDescription, descriptionGot) + } + } + } + + @Test + fun `work when inherited part is empty`() { + testInline( + """ + |/src/main/java/sample/Superclass.java + |package sample; + |public class Superclass { } + | + |/src/main/java/sample/Subclass.java + |package sample; + |/** + |* Subclass docs. {@inheritDoc} End of subclass docs + |*/ + |public class Subclass extends Superclass { } + """.trimIndent(), configuration + ) { + documentablesMergingStage = { module -> + val subclass = module.findClasslike("sample", "Subclass") + val descriptionGot = subclass.documentation.values.first().children.first() + val expectedDescription = Description( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("Subclass docs. End of subclass docs")) + ) + ), + name = "MARKDOWN_FILE" + ) + ) + + assertEquals(expectedDescription, descriptionGot) + } + } + } + + @Test + @Disabled("This should be enabled when we have proper tag inheritance in javadoc parser") + fun `work when inherited part is empty in supertype but present in its supertype`() { + testInline( + """ + |/src/main/java/sample/SuperSuperclass.java + |package sample; + |/** + |* Super super docs + |*/ + |public class SuperSuperclass { } + |/src/main/java/sample/Superclass.java + |package sample; + |public class Superclass extends SuperSuperClass { } + | + |/src/main/java/sample/Subclass.java + |package sample; + |/** + |* Subclass docs. {@inheritDoc} End of subclass docs + |*/ + |public class Subclass extends Superclass { } + """.trimIndent(), configuration + ) { + documentablesMergingStage = { module -> + val subclass = module.findClasslike("sample", "Subclass") + val descriptionGot = subclass.documentation.values.first().children.first() + val expectedDescription = Description( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("Subclass docs. Super super docs End of subclass docs")) + ) + ), + name = "MARKDOWN_FILE" + ) + ) + + assertEquals(expectedDescription, descriptionGot) + } + } + } + + @Test + //Original javadoc doesn't treat interfaces as valid candidates for inherit doc + fun `work with interfaces`() { + testInline( + """ + |/src/main/java/sample/SuperInterface.java + |package sample; + |/** + |* Super super docs + |*/ + |public interface SuperInterface { } + | + |/src/main/java/sample/Subclass.java + |package sample; + |/** + |* Subclass docs. {@inheritDoc} End of subclass docs + |*/ + |public interface Subclass extends SuperInterface { } + """.trimIndent(), configuration + ) { + documentablesMergingStage = { module -> + val subclass = module.findClasslike("sample", "Subclass") + val descriptionGot = subclass.documentation.values.first().children.first() + val expectedDescription = Description( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("Subclass docs. End of subclass docs")) + ) + ), + name = "MARKDOWN_FILE" + ) + ) + + assertEquals(expectedDescription, descriptionGot) + } + } + } + + + @Test + fun `work with multiple supertypes`() { + testInline( + """ + |/src/main/java/sample/SuperInterface.java + |package sample; + |/** + |* Super interface docs + |*/ + |public interface SuperInterface { } + |/src/main/java/sample/Superclass.java + |package sample; + |/** + |* Super class docs + |*/ + |public class Superclass { } + | + |/src/main/java/sample/Subclass.java + |package sample; + |/** + |* Subclass docs. {@inheritDoc} End of subclass docs + |*/ + |public class Subclass extends Superclass implements SuperInterface { } + """.trimIndent(), configuration + ) { + documentablesMergingStage = { module -> + val subclass = module.findClasslike("sample", "Subclass") + val descriptionGot = subclass.documentation.values.first().children.first() + val expectedDescription = Description( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("Subclass docs. Super class docs End of subclass docs")) + ) + ), + name = "MARKDOWN_FILE" + ) + ) + + assertEquals(expectedDescription, descriptionGot) + } + } + } + + @Test + fun `work with methods`() { + testInline( + """ + |/src/main/java/sample/Superclass.java + |package sample; + |public class Superclass { + |/** + |* Sample super method + |* + |* @return super string + |* @throws RuntimeException super throws + |* @see java.lang.String super see + |* @deprecated super deprecated + |*/ + |public String test() { + | return ""; + |} + |} + |/src/main/java/sample/Subclass.java + |package sample; + |class Subclass extends Superclass { + | /** + | * Sample sub method. {@inheritDoc} + | */ + | @Override + | public String test() { + | return super.test(); + | } + |} + """.trimIndent(), configuration + ) { + documentablesMergingStage = { module -> + val function = module.findFunction("sample", "Subclass", "test") + val descriptionGot = function.documentation.values.first().children.first() + val expectedDescription = Description( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("Sample sub method. Sample super method")) + ) + ), + name = "MARKDOWN_FILE" + ) + ) + + assertEquals(expectedDescription, descriptionGot) + } + } + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/translators/JavadocInheritedDocTagsTest.kt b/plugins/base/src/test/kotlin/translators/JavadocInheritedDocTagsTest.kt new file mode 100644 index 00000000..a7d4f057 --- /dev/null +++ b/plugins/base/src/test/kotlin/translators/JavadocInheritedDocTagsTest.kt @@ -0,0 +1,247 @@ +package translators + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.PointingToDeclaration +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.model.doc.* +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.jetbrains.dokka.model.doc.Deprecated as DokkaDeprecatedTag +import org.jetbrains.dokka.model.doc.Throws as DokkaThrowsTag + +class JavadocInheritedDocTagsTest : AbstractCoreTest() { + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/java") + } + } + } + + private fun performTagsTest(test: (DModule) -> Unit) { + testInline( + """ + |/src/main/java/sample/Superclass.java + |package sample; + |/** + |* @author super author + |*/ + |class Superclass { + | /** + | * Sample super method + | * + | * @return super string + | * @throws RuntimeException super throws + | * @see java.lang.String super see + | * @deprecated super deprecated + | */ + | public String test(){ + | return ""; + | } + | + | /** + | * + | * @param xd String superclass + | * @param asd Integer superclass + | */ + | public void test2(String xd, Integer asd){ + | } + |} + |/src/main/java/sample/Subclass.java + |package sample; + |/** + |* @author Ja, {@inheritDoc} + |*/ + |class Subclass extends Superclass { + |/** + | * Sample sub method. {@inheritDoc} + | * + | * @return "sample string". {@inheritDoc} + | * @throws RuntimeException because i can, {@inheritDoc} + | * @throws IllegalStateException this should be it {@inheritDoc} + | * @see java.lang.String string, {@inheritDoc} + | * @deprecated do not use, {@inheritDoc} + | */ + | @Override + | public String test() { + | return super.test(); + | } + | + | /** + | * + | * @param asd2 integer subclass, {@inheritDoc} + | * @param xd2 string subclass, {@inheritDoc} + | */ + | public void test2(String xd2, Integer asd2){ + | } + |} + """.trimIndent(), configuration + ) { + documentablesMergingStage = test + } + } + + @Test + fun `work with return`() { + performTagsTest { module -> + val function = module.findFunction("sample", "Subclass", "test") + val renderedTag = function.documentation.values.first().children.firstIsInstance<Return>() + val expectedTag = Return( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("\"sample string\". super string")) + ) + ), + name = "MARKDOWN_FILE" + ) + ) + + assertEquals(expectedTag, renderedTag) + } + } + + @Test + fun `work with throws`() { + performTagsTest { module -> + val function = module.findFunction("sample", "Subclass", "test") + val renderedTag = + function.documentation.values.first().children.first { it is DokkaThrowsTag && it.name == "java.lang.RuntimeException" } + val expectedTag = DokkaThrowsTag( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("because i can, super throws")) + ) + ), + name = "MARKDOWN_FILE" + ), + "java.lang.RuntimeException", + DRI("java.lang", "RuntimeException", target = PointingToDeclaration) + ) + + assertEquals(expectedTag, renderedTag) + } + } + + @Test + fun `work with throws when exceptions are different`() { + performTagsTest { module -> + val function = module.findFunction("sample", "Subclass", "test") + val renderedTag = + function.documentation.values.first().children.first { it is DokkaThrowsTag && it.name == "java.lang.IllegalStateException" } + val expectedTag = DokkaThrowsTag( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("this should be it")) + ) + ), + name = "MARKDOWN_FILE" + ), + "java.lang.IllegalStateException", + DRI("java.lang", "IllegalStateException", target = PointingToDeclaration) + ) + + assertEquals(expectedTag, renderedTag) + } + } + + @Test + fun `work with deprecated`() { + performTagsTest { module -> + val function = module.findFunction("sample", "Subclass", "test") + val renderedTag = function.documentation.values.first().children.firstIsInstance<DokkaDeprecatedTag>() + val expectedTag = DokkaDeprecatedTag( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("do not use, Sample super method")) + ) + ), + name = "MARKDOWN_FILE" + ), + ) + + assertEquals(expectedTag, renderedTag) + } + } + + @Test + fun `work with see also`() { + performTagsTest { module -> + val function = module.findFunction("sample", "Subclass", "test") + val renderedTag = function.documentation.values.first().children.firstIsInstance<See>() + val expectedTag = See( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("string,")) + ) + ), + name = "MARKDOWN_FILE" + ), + "java.lang.String", + DRI("java.lang", "String") + ) + + assertEquals(expectedTag, renderedTag) + } + } + + @Test + fun `work with author`() { + performTagsTest { module -> + val classlike = module.findClasslike("sample", "Subclass") + val renderedTag = classlike.documentation.values.first().children.firstIsInstance<Author>() + val expectedTag = Author( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("Ja, super author")) + ) + ), + name = "MARKDOWN_FILE" + ), + ) + + assertEquals(expectedTag, renderedTag) + } + } + + @Test + fun `work with params`() { + performTagsTest { module -> + val function = module.findFunction("sample", "Subclass", "test2") + val (asdTag, xdTag) = function.documentation.values.first().children.filterIsInstance<Param>() + .sortedBy { it.name } + + val expectedAsdTag = Param( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("integer subclass, Integer superclass")) + ) + ), + name = "MARKDOWN_FILE" + ), + "asd2" + ) + val expectedXdTag = Param( + CustomDocTag( + children = listOf( + P( + children = listOf(Text("string subclass, String superclass")) + ) + ), + name = "MARKDOWN_FILE" + ), + "xd2" + ) + assertEquals(expectedAsdTag, asdTag) + assertEquals(expectedXdTag, xdTag) + } + } +}
\ No newline at end of file |