Skip to content

Commit

Permalink
Javadoc @inheritdoc tag support
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcinAman committed Nov 12, 2020
1 parent 020acfc commit e8825b3
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 54 deletions.
2 changes: 1 addition & 1 deletion core/src/main/kotlin/utilities/safeEnumValueOf.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.jetbrains.dokka.utilities

inline fun <reified T : Enum<*>> enumValueOrNull(name: String): T? =
T::class.java.enumConstants.firstOrNull { it.name == name }
T::class.java.enumConstants.firstOrNull { it.name.toUpperCase() == name.toUpperCase() }
2 changes: 1 addition & 1 deletion plugins/base/src/main/kotlin/parsers/MarkdownParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ open class MarkdownParser(
See(
parseStringToDocNode(it.getContent()),
dri?.fqName() ?: it.getSubjectName().orEmpty(),
pointedLink(it),
dri,
)
}
KDocKnownTag.SINCE -> Since(parseStringToDocNode(it.getContent()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal class InheritDocResolver(
) {
internal fun resolveFromContext(context: CommentResolutionContext) =
when (context.tag) {
JavadocTag.THROWS -> context.name?.let { name -> resolveThrowsTag(context.comment, name) }
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()
Expand All @@ -40,19 +40,25 @@ internal class InheritDocResolver(
}
}.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, JavadocTag.THROWS) }
(currentElement.owner as? PsiMethod)?.let { method -> lowestMethodsWithTag(method, tag) }
.orEmpty().firstOrNull {
findClosestDocComment(it, logger)?.hasThrowsTagWithExceptionOfType(exceptionFqName) == true
}?.docComment?.tagsByName(JavadocTag.THROWS)?.flatMap {
findClosestDocComment(it, logger)?.hasTagWithExceptionOfType(tag, exceptionFqName) == true
}?.docComment?.tagsByName(tag)?.flatMap {
when (it) {
is PsiDocTag -> it.contentElementsWithSiblingIfNeeded()
else -> listOf(it)
}
}?.drop(1).orEmpty()
}?.withoutReferenceLink().orEmpty()

private fun resolveParamTag(
currentElement: PsiDocComment,
Expand All @@ -73,14 +79,13 @@ internal class InheritDocResolver(
}.orEmpty()
}
}
}.drop(1)
}.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 {
val tag = findClosestDocComment(it, logger)
return if (tag?.hasTag(javadocTag) == true) tag
else lowestClassWithTag(it, javadocTag)
findClosestDocComment(it, logger)?.takeIf { tag -> tag.hasTag(javadocTag) } ?:
lowestClassWithTag(it, javadocTag)
}

private fun lowestMethodWithTag(
Expand All @@ -92,8 +97,10 @@ internal class InheritDocResolver(
private fun lowestMethodsWithTag(baseMethod: PsiMethod, javadocTag: JavadocTag) =
baseMethod.findSuperMethods().filter { findClosestDocComment(it, logger)?.hasTag(javadocTag) == true }

private fun PsiDocComment.hasThrowsTagWithExceptionOfType(exceptionFqName: String): Boolean =
hasTag(JavadocTag.THROWS) && tagsByName(JavadocTag.THROWS).firstIsInstanceOrNull<PsiDocTag>()
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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class JavadocParser(
}

private fun parseDocTag(tag: PsiDocTag, docComment: PsiDocComment, analysedElement: PsiNamedElement): TagWrapper? =
enumValueOrNull<JavadocTag>(tag.name.toUpperCase())?.let { javadocTag ->
enumValueOrNull<JavadocTag>(tag.name)?.let { javadocTag ->
val resolutionContext = CommentResolutionContext(comment = docComment, tag = javadocTag)
when (resolutionContext.tag) {
JavadocTag.PARAM -> {
Expand Down Expand Up @@ -116,11 +116,7 @@ class JavadocParser(
)
)
else -> null
//TODO Missing tags: SERIAL,
// SERIAL_DATA,
// SERIAL_FIELD,
// SINCE,
// VERSION
//TODO https://github.com/Kotlin/dokka/issues/1618
}
}

Expand Down Expand Up @@ -174,10 +170,7 @@ class JavadocParser(
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
)
acc + e.stringify(acc.newState, context)
}
else -> stringifySimpleElement(state, context)
}
Expand All @@ -200,9 +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
}
Expand Down Expand Up @@ -285,10 +277,7 @@ class JavadocParser(
"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
)
result + e.stringify(result.newState, context)
}?.parsedLine.orEmpty()
else -> tag.text
}
Expand Down Expand Up @@ -373,12 +362,7 @@ class JavadocParser(
resolveToGetDri()?.let {
val dri = DRI.from(it)
val label = labelElement ?: defaultLabel()
DocumentationLink(
dri, convertJavadocElements(
listOfNotNull(label), asParagraph = false,
context
)
)
DocumentationLink(dri, convertJavadocElements(listOfNotNull(label), asParagraph = false, context))
}

private fun PsiDocTag.referenceElement(): PsiElement? =
Expand Down
28 changes: 14 additions & 14 deletions plugins/base/src/test/kotlin/translators/JavadocInheritDocsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
}

@Test
fun `work when whole description is inherited`(){
fun `work when whole description is inherited`() {
testInline(
"""
|/src/main/java/sample/Superclass.java
Expand All @@ -37,7 +37,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
|*/
|public class Subclass extends Superclass { }
""".trimIndent(), configuration
){
) {
documentablesMergingStage = { module ->
val subclass = module.findClasslike("sample", "Subclass")
val descriptionGot = subclass.documentation.values.first().children.first()
Expand All @@ -58,7 +58,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
}

@Test
fun `work when inherited part is inside description`(){
fun `work when inherited part is inside description`() {
testInline(
"""
|/src/main/java/sample/Superclass.java
Expand All @@ -75,7 +75,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
|*/
|public class Subclass extends Superclass { }
""".trimIndent(), configuration
){
) {
documentablesMergingStage = { module ->
val subclass = module.findClasslike("sample", "Subclass")
val descriptionGot = subclass.documentation.values.first().children.first()
Expand All @@ -96,7 +96,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
}

@Test
fun `work when inherited part is empty`(){
fun `work when inherited part is empty`() {
testInline(
"""
|/src/main/java/sample/Superclass.java
Expand All @@ -110,7 +110,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
|*/
|public class Subclass extends Superclass { }
""".trimIndent(), configuration
){
) {
documentablesMergingStage = { module ->
val subclass = module.findClasslike("sample", "Subclass")
val descriptionGot = subclass.documentation.values.first().children.first()
Expand All @@ -132,7 +132,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {

@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`(){
fun `work when inherited part is empty in supertype but present in its supertype`() {
testInline(
"""
|/src/main/java/sample/SuperSuperclass.java
Expand All @@ -152,7 +152,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
|*/
|public class Subclass extends Superclass { }
""".trimIndent(), configuration
){
) {
documentablesMergingStage = { module ->
val subclass = module.findClasslike("sample", "Subclass")
val descriptionGot = subclass.documentation.values.first().children.first()
Expand All @@ -174,7 +174,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {

@Test
//Original javadoc doesn't treat interfaces as valid candidates for inherit doc
fun `work with interfaces`(){
fun `work with interfaces`() {
testInline(
"""
|/src/main/java/sample/SuperInterface.java
Expand All @@ -191,7 +191,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
|*/
|public interface Subclass extends SuperInterface { }
""".trimIndent(), configuration
){
) {
documentablesMergingStage = { module ->
val subclass = module.findClasslike("sample", "Subclass")
val descriptionGot = subclass.documentation.values.first().children.first()
Expand All @@ -213,7 +213,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {


@Test
fun `work with multiple supertypes`(){
fun `work with multiple supertypes`() {
testInline(
"""
|/src/main/java/sample/SuperInterface.java
Expand All @@ -236,7 +236,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
|*/
|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()
Expand All @@ -257,7 +257,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
}

@Test
fun `work with methods`(){
fun `work with methods`() {
testInline(
"""
|/src/main/java/sample/Superclass.java
Expand Down Expand Up @@ -287,7 +287,7 @@ class JavadocInheritDocsTest : AbstractCoreTest() {
| }
|}
""".trimIndent(), configuration
){
) {
documentablesMergingStage = { module ->
val function = module.findFunction("sample", "Subclass", "test")
val descriptionGot = function.documentation.values.first().children.first()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ class JavadocInheritedDocTagsTest : AbstractCoreTest() {
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 renderedTag =
function.documentation.values.first().children.first { it is DokkaThrowsTag && it.name == "java.lang.RuntimeException" }
val expectedTag = DokkaThrowsTag(
CustomDocTag(
children = listOf(
Expand All @@ -129,7 +130,8 @@ class JavadocInheritedDocTagsTest : AbstractCoreTest() {
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 renderedTag =
function.documentation.values.first().children.first { it is DokkaThrowsTag && it.name == "java.lang.IllegalStateException" }
val expectedTag = DokkaThrowsTag(
CustomDocTag(
children = listOf(
Expand Down Expand Up @@ -210,10 +212,11 @@ class JavadocInheritedDocTagsTest : AbstractCoreTest() {
}

@Test
fun `work with params`(){
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 (asdTag, xdTag) = function.documentation.values.first().children.filterIsInstance<Param>()
.sortedBy { it.name }

val expectedAsdTag = Param(
CustomDocTag(
Expand Down

0 comments on commit e8825b3

Please sign in to comment.