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 7, 2020
1 parent 1aba0ec commit efc7040
Show file tree
Hide file tree
Showing 12 changed files with 968 additions and 207 deletions.
1 change: 1 addition & 0 deletions core/src/main/kotlin/model/doc/TagWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ data class Param(override val root: DocTag, override val name: String) : NamedTa
data class Return(override val root: DocTag) : TagWrapper()
data class Receiver(override val root: DocTag) : TagWrapper()
data class Constructor(override val root: DocTag) : TagWrapper()
//TODO this naming is confusing since kotlin has Throws annotation
data class Throws(override val root: DocTag, override val name: String, val exceptionAddress: DRI?) : NamedTagWrapper()
data class Sample(override val root: DocTag, override val name: String) : NamedTagWrapper()
data class Deprecated(override val root: DocTag) : TagWrapper()
Expand Down
50 changes: 0 additions & 50 deletions core/src/main/kotlin/utilities/nodeDebug.kt

This file was deleted.

4 changes: 4 additions & 0 deletions core/src/main/kotlin/utilities/safeEnumValueOf.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.jetbrains.dokka.utilities

inline fun <reified T : Enum<*>> enumValueOrNull(name: String): T? =
T::class.java.enumConstants.firstOrNull { it.name == name }
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import org.jetbrains.dokka.analysis.KotlinAnalysis
import org.jetbrains.dokka.analysis.PsiDocumentableSource
import org.jetbrains.dokka.analysis.from
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
Expand Down
47 changes: 47 additions & 0 deletions plugins/base/src/main/kotlin/translators/psi/PsiInheritance.kt
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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 -> context.name?.let { name -> resolveThrowsTag(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()

private fun resolveThrowsTag(
currentElement: PsiDocComment,
exceptionFqName: String
): List<PsiElement> =
(currentElement.owner as? PsiMethod)?.let { method -> lowestMethodsWithTag(method, JavadocTag.THROWS) }
.orEmpty().firstOrNull {
findClosestDocComment(it, logger)?.hasThrowsTagWithExceptionOfType(exceptionFqName) == true
}?.docComment?.tagsByName(JavadocTag.THROWS)?.flatMap {
when (it) {
is PsiDocTag -> it.contentElementsWithSiblingIfNeeded()
else -> listOf(it)
}
}?.drop(1).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()
}
}
}.drop(1)

//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)
}

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.hasThrowsTagWithExceptionOfType(exceptionFqName: String): Boolean =
hasTag(JavadocTag.THROWS) && tagsByName(JavadocTag.THROWS).firstIsInstanceOrNull<PsiDocTag>()
?.resolveException()
?.getKotlinFqName()?.asString() == exceptionFqName
}

0 comments on commit efc7040

Please sign in to comment.