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 a5bd1e8
Show file tree
Hide file tree
Showing 14 changed files with 985 additions and 220 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 }
18 changes: 11 additions & 7 deletions plugins/base/src/main/kotlin/parsers/MarkdownParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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" -> {
Expand Down Expand Up @@ -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(),
pointedLink(it),
)
}
KDocKnownTag.SINCE -> Since(parseStringToDocNode(it.getContent()))
KDocKnownTag.CONSTRUCTOR -> Constructor(parseStringToDocNode(it.getContent()))
KDocKnownTag.PROPERTY -> Property(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ open class DefaultPageCreator(
tags.groupBy { it::class }.forEach {
(_, sameCategoryTags) ->
group(sourceSets = setOf(platform), styles = emptySet()) {
header(4, sameCategoryTags.first().toHeaderString())
header(3, sameCategoryTags.first().toHeaderString())
sameCategoryTags.forEach { comment(it.root) }
}
}
Expand All @@ -355,7 +355,7 @@ open class DefaultPageCreator(

fun DocumentableContentBuilder.contentForParams() {
if (tags.isNotEmptyForTag<Param>()) {
header(2, "Parameters", kind = ContentKind.Parameters)
header(3, "Parameters", kind = ContentKind.Parameters)
group(
extra = mainExtra + SimpleAttr.header("Parameters"),
styles = setOf(ContentStyle.WithExtraAttributes)
Expand Down Expand Up @@ -396,7 +396,7 @@ open class DefaultPageCreator(

fun DocumentableContentBuilder.contentForSeeAlso() {
if (tags.isNotEmptyForTag<See>()) {
header(2, "See also", kind = ContentKind.Comment)
header(3, "See also", kind = ContentKind.Comment)
group(
extra = mainExtra + SimpleAttr.header("See also"),
styles = setOf(ContentStyle.WithExtraAttributes)
Expand Down Expand Up @@ -432,7 +432,7 @@ open class DefaultPageCreator(
fun DocumentableContentBuilder.contentForThrows() {
val throws = tags.withTypeNamed<Throws>()
if (throws.isNotEmpty()) {
header(4, "Throws")
header(3, "Throws")
sourceSetDependentHint(sourceSets = platforms.toSet(), kind = ContentKind.SourceSetDependentHint) {
platforms.forEach { sourceset ->
table(kind = ContentKind.Main, sourceSets = setOf(sourceset)) {
Expand All @@ -457,7 +457,7 @@ open class DefaultPageCreator(
fun DocumentableContentBuilder.contentForSamples() {
val samples = tags.withTypeNamed<Sample>()
if (samples.isNotEmpty()) {
header(2, "Samples", kind = ContentKind.Sample)
header(3, "Samples", kind = ContentKind.Sample)
group(
extra = mainExtra + SimpleAttr.header("Samples"),
styles = emptySet()
Expand Down
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>()
?.resolveToElement()
?.getKotlinFqName()?.asString() == exceptionFqName
}

0 comments on commit a5bd1e8

Please sign in to comment.