44
55package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc
66
7- import com.intellij.psi.PsiElement
87import com.intellij.psi.PsiNamedElement
9- import com.intellij.psi.PsiRecursiveElementWalkingVisitor
10- import com.intellij.psi.util.PsiTreeUtil
118import org.jetbrains.dokka.analysis.java.parsers.JavadocParser
129import org.jetbrains.dokka.model.doc.DocumentationNode
1310import org.jetbrains.dokka.utilities.DokkaLogger
11+ import org.jetbrains.kotlin.analysis.api.KaNonPublicApi
1412import org.jetbrains.kotlin.analysis.api.KaSession
13+ import org.jetbrains.kotlin.analysis.api.analyze
1514import org.jetbrains.kotlin.analysis.api.symbols.*
1615import org.jetbrains.kotlin.analysis.api.symbols.markers.KaNamedSymbol
17- import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
18- import org.jetbrains.kotlin.kdoc.psi.api.KDoc
19- import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
20- import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
2116import org.jetbrains.kotlin.psi.*
22- import org.jetbrains.kotlin.psi.psiUtil.checkDecompiledText
23- import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
24- import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
25- import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter
26- import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
2717
2818internal fun KaSession.getJavaDocDocumentationFrom (
2919 symbol : KaSymbol ,
@@ -48,8 +38,8 @@ internal fun KaSession.getJavaDocDocumentationFrom(
4838 return null
4939}
5040
51- internal fun KaSession. getKDocDocumentationFrom ( symbol : KaSymbol , logger : DokkaLogger ) = findKDoc(symbol)?. let { kDocContent ->
52-
41+ @OptIn( KaNonPublicApi :: class , KtNonPublicApi :: class )
42+ internal fun KaSession. getKDocDocumentationFrom ( symbol : KaSymbol , logger : DokkaLogger ) = (symbol as ? KaDeclarationSymbol )?.findKDoc()?. let { kDocContent ->
5343 val ktElement = symbol.psi
5444 val kdocLocation = ktElement?.containingFile?.name?.let {
5545 val name = when (symbol) {
@@ -65,162 +55,11 @@ internal fun KaSession.getKDocDocumentationFrom(symbol: KaSymbol, logger: DokkaL
6555
6656
6757 parseFromKDocTag(
68- kDocTag = kDocContent.contentTag ,
58+ kDocTag = kDocContent.primaryTag ,
6959 externalDri = { link -> resolveKDocLinkToDRI(link).ifUnresolved { logger.logUnresolvedLink(link.getLinkText(), kdocLocation) } },
7060 kdocLocation = kdocLocation
7161 )
7262}
7363
74-
75-
76-
77- // ----------- copy-paste from IDE ----------------------------------------------------------------------------
78-
79- internal data class KDocContent (
80- val contentTag : KDocTag ,
81- val sections : List <KDocSection >
82- )
83-
84- internal fun KaSession.findKDoc (symbol : KaSymbol ): KDocContent ? {
85- // Dokka's HACK: primary constructors can be generated
86- // so [KtSymbol.psi] is undefined for [KtSymbolOrigin.SOURCE_MEMBER_GENERATED] origin
87- // we need to get psi of a containing class
88- if (symbol is KaConstructorSymbol && symbol.isPrimary) {
89- val containingClass = symbol.fakeOverrideOriginal.containingSymbol as ? KaClassSymbol
90- if (containingClass?.origin != KaSymbolOrigin .SOURCE ) return null
91- val kdoc = (containingClass.psi as ? KtDeclaration )?.docComment ? : return null
92-
93- // duplicates the logic of IDE's `lookupOwnedKDoc`
94- val constructorSection = kdoc.findSectionByTag(KDocKnownTag .CONSTRUCTOR )
95- if (constructorSection != null ) {
96- // if annotated with @constructor tag and the caret is on constructor definition,
97- // then show @constructor description as the main content, and additional sections
98- // that contain @param tags (if any), as the most relatable ones
99- // practical example: val foo = Fo<caret>o("argument") -- show @constructor and @param content
100- val paramSections = kdoc.findSectionsContainingTag(KDocKnownTag .PARAM )
101- return KDocContent (constructorSection, paramSections)
102- }
103- return KDocContent (kdoc.getDefaultSection(), kdoc.getAllSections())
104- }
105-
106- // for generated function (e.g. `copy`) [KtSymbol.psi] is undefined (although actually returns a class psi), see test `data class kdocs over generated methods`
107- // for DELEGATED/INTERSECTION_OVERRIDE/SUBSTITUTION_OVERRIDE members, it continues search in overridden symbols
108- val ktElement = if (symbol.origin == KaSymbolOrigin .SOURCE ) symbol.psi as ? KtElement else null
109- ktElement?.findKDoc()?.let {
110- return it
111- }
112-
113- if (symbol is KaCallableSymbol ) {
114- // TODO https://youtrack.jetbrains.com/issue/KT-70326/Analysis-API-Inconsistent-allOverriddenSymbols-and-directlyOverriddenSymbols-for-an-intersection-symbol
115- val allOverriddenSymbolsWithIntersection = symbol.intersectionOverriddenSymbols.filterNot { it == symbol }.asSequence() + symbol.allOverriddenSymbols
116-
117- allOverriddenSymbolsWithIntersection.forEach { overrider ->
118- findKDoc(overrider)?.let {
119- return it
120- }
121- }
122- }
123- return null
124- }
125-
126-
127- internal fun KtElement.findKDoc (): KDocContent ? = this .lookupOwnedKDoc()
128- ? : this .lookupKDocInContainer()
129-
130-
131-
132- private fun KtElement.lookupOwnedKDoc (): KDocContent ? {
133- // KDoc for primary constructor is located inside of its class KDoc
134- val psiDeclaration = when (this ) {
135- is KtPrimaryConstructor -> getContainingClassOrObject()
136- else -> this
137- }
138-
139- if (psiDeclaration is KtDeclaration ) {
140- val kdoc = psiDeclaration.docComment
141- if (kdoc != null ) {
142- if (this is KtConstructor <* >) {
143- // ConstructorDescriptor resolves to the same JetDeclaration
144- val constructorSection = kdoc.findSectionByTag(KDocKnownTag .CONSTRUCTOR )
145- if (constructorSection != null ) {
146- // if annotated with @constructor tag and the caret is on constructor definition,
147- // then show @constructor description as the main content, and additional sections
148- // that contain @param tags (if any), as the most relatable ones
149- // practical example: val foo = Fo<caret>o("argument") -- show @constructor and @param content
150- val paramSections = kdoc.findSectionsContainingTag(KDocKnownTag .PARAM )
151- return KDocContent (constructorSection, paramSections)
152- }
153- }
154- return KDocContent (kdoc.getDefaultSection(), kdoc.getAllSections())
155- }
156- }
157-
158- return null
159- }
160-
161- /* *
162- * Looks for sections that have a deeply nested [tag],
163- * as opposed to [KDoc.findSectionByTag], which only looks among the top level
164- */
165- private fun KDoc.findSectionsContainingTag (tag : KDocKnownTag ): List <KDocSection > {
166- return getChildrenOfType<KDocSection >()
167- .filter { it.findTagByName(tag.name.toLowerCaseAsciiOnly()) != null }
168- }
169-
170- private fun KtElement.lookupKDocInContainer (): KDocContent ? {
171- val subjectName = name
172- val containingDeclaration =
173- PsiTreeUtil .findFirstParent(this , true ) {
174- it is KtDeclarationWithBody && it !is KtPrimaryConstructor
175- || it is KtClassOrObject
176- }
177-
178- val containerKDoc = containingDeclaration?.getChildOfType<KDoc >()
179- if (containerKDoc == null || subjectName == null ) return null
180- val propertySection = containerKDoc.findSectionByTag(KDocKnownTag .PROPERTY , subjectName)
181- val paramTag =
182- containerKDoc.findDescendantOfType<KDocTag > { it.knownTag == KDocKnownTag .PARAM && it.getSubjectName() == subjectName }
183-
184- val primaryContent = when {
185- // class Foo(val <caret>s: String)
186- this is KtParameter && this .isPropertyParameter() -> propertySection ? : paramTag
187- // fun some(<caret>f: String) || class Some<<caret>T: Base> || Foo(<caret>s = "argument")
188- this is KtParameter || this is KtTypeParameter -> paramTag
189- // if this property is declared separately (outside primary constructor), but it's for some reason
190- // annotated as @property in class's description, instead of having its own KDoc
191- this is KtProperty && containingDeclaration is KtClassOrObject -> propertySection
192- else -> null
193- }
194- return primaryContent?.let {
195- // makes little sense to include any other sections, since we found
196- // documentation for a very specific element, like a property/param
197- KDocContent (it, sections = emptyList())
198- }
199- }
200-
201- private inline fun <reified T : PsiElement > PsiElement.findDescendantOfType (noinline predicate : (T ) -> Boolean = { true }): T ? {
202- return findDescendantOfType({ true }, predicate)
203- }
204-
205- private inline fun <reified T : PsiElement > PsiElement.findDescendantOfType (
206- crossinline canGoInside : (PsiElement ) -> Boolean ,
207- noinline predicate : (T ) -> Boolean = { true }
208- ): T ? {
209- checkDecompiledText()
210- var result: T ? = null
211- this .accept(object : PsiRecursiveElementWalkingVisitor () {
212- override fun visitElement (element : PsiElement ) {
213- if (element is T && predicate(element)) {
214- result = element
215- stopWalking()
216- return
217- }
218-
219- if (canGoInside(element)) {
220- super .visitElement(element)
221- }
222- }
223- })
224- return result
225- }
226-
64+ @OptIn(KtNonPublicApi ::class , KaNonPublicApi ::class )
65+ internal fun KtDeclaration.findKDoc () = analyze(this ) { this @findKDoc.findKDoc() }
0 commit comments