-
Notifications
You must be signed in to change notification settings - Fork 419
/
Copy pathPsiCommentsUtils.kt
146 lines (121 loc) · 6.04 KB
/
PsiCommentsUtils.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.kdoc.findKDoc
import org.jetbrains.kotlin.idea.base.utils.fqname.getKotlinFqName
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
internal interface DocComment {
fun hasTag(tag: JavadocTag): Boolean
fun hasTagWithExceptionOfType(tag: JavadocTag, exceptionFqName: String): Boolean
fun tagsByName(tag: JavadocTag, param: String? = null): List<DocumentationContent>
}
internal data class JavaDocComment(val comment: PsiDocComment) : DocComment {
override fun hasTag(tag: JavadocTag): Boolean = comment.hasTag(tag)
override fun hasTagWithExceptionOfType(tag: JavadocTag, exceptionFqName: String): Boolean =
comment.hasTag(tag) && comment.tagsByName(tag).firstIsInstanceOrNull<PsiDocTag>()
?.resolveToElement()
?.getKotlinFqName()?.asString() == exceptionFqName
override fun tagsByName(tag: JavadocTag, param: String?): List<DocumentationContent> =
comment.tagsByName(tag).map { PsiDocumentationContent(it, tag) }
}
internal data class KotlinDocComment(val comment: KDocTag, val descriptor: DeclarationDescriptor) : DocComment {
override fun hasTag(tag: JavadocTag): Boolean =
when (tag) {
JavadocTag.DESCRIPTION -> comment.getContent().isNotEmpty()
else -> tagsWithContent.any { it.text.startsWith("@$tag") }
}
override fun hasTagWithExceptionOfType(tag: JavadocTag, exceptionFqName: String): Boolean =
tagsWithContent.any { it.hasExceptionWithName(tag, exceptionFqName) }
override fun tagsByName(tag: JavadocTag, param: String?): List<DocumentationContent> =
when (tag) {
JavadocTag.DESCRIPTION -> listOf(DescriptorDocumentationContent(descriptor, comment, tag))
else -> comment.children.mapNotNull { (it as? KDocTag) }
.filter { it.name == "$tag" && param?.let { param -> it.hasExceptionWithName(param) } != false }
.map { DescriptorDocumentationContent(descriptor, it, tag) }
}
private val tagsWithContent: List<KDocTag> = comment.children.mapNotNull { (it as? KDocTag) }
private fun KDocTag.hasExceptionWithName(tag: JavadocTag, exceptionFqName: String) =
text.startsWith("@$tag") && hasExceptionWithName(exceptionFqName)
private fun KDocTag.hasExceptionWithName(exceptionFqName: String) =
getSubjectName() == exceptionFqName
}
internal interface DocumentationContent {
val tag: JavadocTag
}
internal data class PsiDocumentationContent(val psiElement: PsiElement, override val tag: JavadocTag) :
DocumentationContent
internal data class DescriptorDocumentationContent(
val descriptor: DeclarationDescriptor,
val element: KDocTag,
override val tag: JavadocTag
) : DocumentationContent
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): DocComment? {
(element as? PsiDocCommentOwner)?.docComment?.run { return JavaDocComment(this) }
element.toKdocComment()?.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) }.distinct()
if (superMethodDocumentation.size == 1) {
return superMethodDocumentation.single()
}
logger.debug(
"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>()?.let { JavaDocComment(it) }
}
internal fun PsiNamedElement.toKdocComment(): KotlinDocComment? =
(navigationElement as? KtElement)?.findKDoc { DescriptorToSourceUtils.descriptorToDeclaration(it) }
?.run {
([email protected] as? KtDeclaration)?.descriptor?.let {
KotlinDocComment(
this,
it
)
}
}
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()