Skip to content

Commit c6db331

Browse files
authored
New KDoc api (#4363)
* Update Analysis API version and use new KDoc API * Enable multiplatform mode for Analysis API when the project has at least one non-jvm source set * Test for #4245 and #2493 * Update tests to consider new K2 KDoc resolve * Add issue number to the conditional annotations on tests
1 parent d5ad8dd commit c6db331

File tree

9 files changed

+431
-200
lines changed

9 files changed

+431
-200
lines changed

dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt

Lines changed: 7 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,16 @@
44

55
package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc
66

7-
import com.intellij.psi.PsiElement
87
import com.intellij.psi.PsiNamedElement
9-
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
10-
import com.intellij.psi.util.PsiTreeUtil
118
import org.jetbrains.dokka.analysis.java.parsers.JavadocParser
129
import org.jetbrains.dokka.model.doc.DocumentationNode
1310
import org.jetbrains.dokka.utilities.DokkaLogger
11+
import org.jetbrains.kotlin.analysis.api.KaNonPublicApi
1412
import org.jetbrains.kotlin.analysis.api.KaSession
13+
import org.jetbrains.kotlin.analysis.api.analyze
1514
import org.jetbrains.kotlin.analysis.api.symbols.*
1615
import 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
2116
import 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

2818
internal 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() }

dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ import com.intellij.psi.PsiNamedElement
88
import org.jetbrains.dokka.analysis.java.doccomment.DocComment
99
import org.jetbrains.dokka.analysis.java.doccomment.DocCommentCreator
1010
import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.findKDoc
11+
import org.jetbrains.kotlin.analysis.api.KaNonPublicApi
12+
import org.jetbrains.kotlin.psi.KtDeclaration
1113
import org.jetbrains.kotlin.psi.KtElement
14+
import org.jetbrains.kotlin.psi.KtNonPublicApi
1215

1316
internal class DescriptorKotlinDocCommentCreator : DocCommentCreator {
17+
@OptIn(KtNonPublicApi::class, KaNonPublicApi::class)
1418
override fun create(element: PsiNamedElement): DocComment? {
1519
val ktElement = element.navigationElement as? KtElement ?: return null
16-
val kdoc = ktElement.findKDoc() ?: return null
20+
val kdoc = (ktElement as? KtDeclaration)?.findKDoc() ?: return null
1721

18-
return KotlinDocComment(kdoc.contentTag, ResolveDocContext(ktElement))
22+
return KotlinDocComment(kdoc.primaryTag, ResolveDocContext(ktElement))
1923
}
2024
}

dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/KotlinAnalysis.kt

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSdkModule
1818
import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSourceModule
1919
import org.jetbrains.kotlin.config.AnalysisFlags
2020
import org.jetbrains.kotlin.config.ApiVersion
21+
import org.jetbrains.kotlin.config.LanguageFeature
2122
import org.jetbrains.kotlin.config.LanguageVersion
2223
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
2324
import org.jetbrains.kotlin.platform.CommonPlatforms
@@ -50,19 +51,25 @@ private fun getJdkHomeFromSystemProperty(logger: DokkaLogger): File? {
5051

5152
internal fun getLanguageVersionSettings(
5253
languageVersionString: String?,
53-
apiVersionString: String?
54+
apiVersionString: String?,
55+
isMultiplatformProject: Boolean,
5456
): LanguageVersionSettingsImpl {
55-
val languageVersion = LanguageVersion.fromVersionString(languageVersionString) ?: LanguageVersion.LATEST_STABLE
56-
val apiVersion =
57-
apiVersionString?.let { ApiVersion.parse(it) } ?: ApiVersion.createByLanguageVersion(languageVersion)
57+
val languageVersion = LanguageVersion.fromVersionString(languageVersionString)
58+
?: LanguageVersion.LATEST_STABLE
59+
val apiVersion = apiVersionString?.let { ApiVersion.parse(it) }
60+
?: ApiVersion.createByLanguageVersion(languageVersion)
5861
return LanguageVersionSettingsImpl(
5962
languageVersion = languageVersion,
60-
apiVersion = apiVersion, analysisFlags = hashMapOf(
63+
apiVersion = apiVersion,
64+
analysisFlags = hashMapOf(
6165
AnalysisFlags.allowKotlinPackage to InternalConfiguration.allowKotlinPackage,
6266
// special flag for Dokka
6367
// force to resolve light classes (lazily by default)
64-
AnalysisFlags.eagerResolveOfLightClasses to true
65-
)
68+
AnalysisFlags.eagerResolveOfLightClasses to true,
69+
),
70+
specificFeatures = listOfNotNull(
71+
if (isMultiplatformProject) LanguageFeature.MultiPlatformProjects to LanguageFeature.State.ENABLED else null
72+
).toMap()
6673
)
6774
}
6875

@@ -73,6 +80,7 @@ internal fun createAnalysisSession(
7380
isSampleProject: Boolean = false
7481
): KotlinAnalysis {
7582
val sourcesModule = mutableMapOf<DokkaConfiguration.DokkaSourceSet, KaSourceModule>()
83+
val isMultiplatformProject = sourceSets.any { it.analysisPlatform != Platform.jvm }
7684

7785
val analysisSession = buildStandaloneAnalysisAPISession(
7886
projectDisposable = projectDisposable,
@@ -116,8 +124,11 @@ internal fun createAnalysisSession(
116124
for (sourceSet in sortedSourceSets) {
117125
val targetPlatform = sourceSet.analysisPlatform.toTargetPlatform()
118126
val sourceModule = buildKtSourceModule {
119-
languageVersionSettings =
120-
getLanguageVersionSettings(sourceSet.languageVersion, sourceSet.apiVersion)
127+
languageVersionSettings = getLanguageVersionSettings(
128+
languageVersionString = sourceSet.languageVersion,
129+
apiVersionString = sourceSet.apiVersion,
130+
isMultiplatformProject = isMultiplatformProject,
131+
)
121132
platform = targetPlatform
122133
moduleName = "<module ${sourceSet.displayName}>"
123134

@@ -161,8 +172,8 @@ internal fun topologicalSortByDependantSourceSets(
161172
val result = mutableListOf<DokkaConfiguration.DokkaSourceSet>()
162173

163174
val verticesAssociatedWithState = sourceSets.associateWithTo(mutableMapOf()) { State.UNVISITED }
164-
fun dfs(souceSet: DokkaConfiguration.DokkaSourceSet) {
165-
when (verticesAssociatedWithState[souceSet]) {
175+
fun dfs(sourceSet: DokkaConfiguration.DokkaSourceSet) {
176+
when (verticesAssociatedWithState[sourceSet]) {
166177
State.VISITED -> return
167178
State.VISITING -> {
168179
logger.error("Detected cycle in source set graph")
@@ -171,16 +182,17 @@ internal fun topologicalSortByDependantSourceSets(
171182

172183
else -> {
173184
val dependentSourceSets =
174-
souceSet.dependentSourceSets.mapNotNull { dependentSourceSetId ->
175-
sourceSets.find { it.sourceSetID == dependentSourceSetId } ?: run {
185+
sourceSet.dependentSourceSets.mapNotNull { dependentSourceSetId ->
186+
sourceSets.find { it.sourceSetID == dependentSourceSetId } ?: run {
176187
logger.error("Cannot find source set with id $dependentSourceSetId")
177-
null }
188+
null
189+
}
178190

179191
}
180-
verticesAssociatedWithState[souceSet] = State.VISITING
192+
verticesAssociatedWithState[sourceSet] = State.VISITING
181193
dependentSourceSets.forEach(::dfs)
182-
verticesAssociatedWithState[souceSet] = State.VISITED
183-
result += souceSet
194+
verticesAssociatedWithState[sourceSet] = State.VISITED
195+
result += sourceSet
184196
}
185197
}
186198
}

dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/translators/documentables/DefaultPageCreator.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -619,13 +619,13 @@ public open class DefaultPageCreator(
619619

620620
protected open fun contentForMember(d: Documentable): ContentGroup = contentForMembers(listOf(d))
621621

622-
protected open fun contentForMembers(doumentables: List<Documentable>): ContentGroup =
623-
contentBuilder.contentFor(doumentables.dri, doumentables.sourceSets) {
622+
protected open fun contentForMembers(documentables: List<Documentable>): ContentGroup =
623+
contentBuilder.contentFor(documentables.dri, documentables.sourceSets) {
624624
group(kind = ContentKind.Cover) {
625-
cover(doumentables.first().name.orEmpty())
625+
cover(documentables.first().name.orEmpty())
626626
}
627627
divergentGroup(ContentDivergentGroup.GroupID("member")) {
628-
doumentables.forEach { d ->
628+
documentables.forEach { d ->
629629
instance(setOf(d.dri), d.sourceSets) {
630630
divergent {
631631
+buildSignature(d)

0 commit comments

Comments
 (0)