diff --git a/build.gradle.kts b/build.gradle.kts index 2ddd8851..b46895eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "2.0.10" apply false + kotlin("jvm") version "2.0.20" apply false id("com.github.johnrengelman.shadow") version "8.1.1" apply false id("org.jlleitschuh.gradle.ktlint") version "12.1.1" } diff --git a/doc-processor-gradle-plugin/build.gradle.kts b/doc-processor-gradle-plugin/build.gradle.kts index f0e75556..7b99ae1d 100644 --- a/doc-processor-gradle-plugin/build.gradle.kts +++ b/doc-processor-gradle-plugin/build.gradle.kts @@ -1,4 +1,5 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -38,15 +39,15 @@ dependencies { shadow(gradleKotlinDsl()) // Dokka dependencies - val dokkaVersion = "1.8.10" - shadow("org.jetbrains.dokka:dokka-analysis:$dokkaVersion") + val dokkaVersion = "2.0.0-Beta" + shadow("org.jetbrains.dokka:analysis-kotlin-symbols:$dokkaVersion") shadow("org.jetbrains.dokka:dokka-base:$dokkaVersion") shadow("org.jetbrains.dokka:dokka-core:$dokkaVersion") shadow("org.jetbrains.dokka:dokka-base-test-utils:$dokkaVersion") shadow("org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion") // logging - api("io.github.microutils:kotlin-logging:3.0.5") + api("io.github.oshai:kotlin-logging:7.0.0") // Use JUnit test framework for unit tests testImplementation(kotlin("test")) @@ -111,7 +112,7 @@ tasks.check { } tasks.withType { - kotlinOptions.jvmTarget = "11" + compilerOptions.jvmTarget = JvmTarget.JVM_11 } java { diff --git a/doc-processor-gradle-plugin/src/functionalTest/kotlin/nl/jolanrensen/docProcessor/DocProcessorFunctionalTest.kt b/doc-processor-gradle-plugin/src/functionalTest/kotlin/nl/jolanrensen/docProcessor/DocProcessorFunctionalTest.kt index 90aa5078..29c3c337 100644 --- a/doc-processor-gradle-plugin/src/functionalTest/kotlin/nl/jolanrensen/docProcessor/DocProcessorFunctionalTest.kt +++ b/doc-processor-gradle-plugin/src/functionalTest/kotlin/nl/jolanrensen/docProcessor/DocProcessorFunctionalTest.kt @@ -81,7 +81,7 @@ abstract class DocProcessorFunctionalTest(name: String) { import nl.jolanrensen.docProcessor.defaultProcessors.* plugins { - kotlin("jvm") version "1.9.21" + kotlin("jvm") version "2.0.20" id("nl.jolanrensen.docProcessor") version "$version" } diff --git a/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/DocumentableWrapperDokka.kt b/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/DocumentableWrapperDokka.kt index fbea2220..005db08b 100644 --- a/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/DocumentableWrapperDokka.kt +++ b/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/DocumentableWrapperDokka.kt @@ -1,6 +1,8 @@ package nl.jolanrensen.docProcessor import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiNamedElement +import org.jetbrains.dokka.InternalDokkaApi import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.annotations import org.jetbrains.dokka.model.AnnotationValue import org.jetbrains.dokka.model.Documentable @@ -21,13 +23,14 @@ import java.io.File * This represents the source of the [documentable] pointing to a language-specific AST/PSI. * @param [logger] [Dokka logger][DokkaLogger] that's needed for [findClosestDocComment]. Should be given. */ +@OptIn(InternalDokkaApi::class) fun DocumentableWrapper.Companion.createFromDokkaOrNull( documentable: Documentable, source: DocumentableSource, logger: DokkaLogger, ): DocumentableWrapper? { val docComment = findClosestDocComment( - element = source.psi, + element = source.psi as PsiNamedElement, logger = logger, ) diff --git a/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/DokkaUtils.kt b/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/DokkaUtils.kt index 1abe3a28..e265d8ab 100644 --- a/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/DokkaUtils.kt +++ b/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/DokkaUtils.kt @@ -1,3 +1,5 @@ +@file:OptIn(InternalDokkaApi::class) + package nl.jolanrensen.docProcessor /* @@ -14,54 +16,57 @@ import com.intellij.psi.PsiDocCommentOwner import com.intellij.psi.PsiElement import com.intellij.psi.PsiJavaCodeReferenceElement import com.intellij.psi.PsiJavaFile +import com.intellij.psi.PsiMember import com.intellij.psi.PsiMethod import com.intellij.psi.PsiNamedElement +import com.intellij.psi.PsiPackage +import com.intellij.psi.PsiQualifiedNamedElement import com.intellij.psi.impl.source.tree.JavaDocElementType import com.intellij.psi.javadoc.PsiDocComment import com.intellij.psi.javadoc.PsiDocTag import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.dokka.analysis.DescriptorDocumentableSource -import org.jetbrains.dokka.analysis.PsiDocumentableSource -import org.jetbrains.dokka.analysis.from +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.analysis.java.DescriptionJavadocTag +import org.jetbrains.dokka.analysis.java.ExceptionJavadocTag +import org.jetbrains.dokka.analysis.java.JavadocTag +import org.jetbrains.dokka.analysis.java.ParamJavadocTag +import org.jetbrains.dokka.analysis.java.ThrowingExceptionJavadocTag +import org.jetbrains.dokka.analysis.java.ThrowsJavadocTag +import org.jetbrains.dokka.analysis.java.doccomment.DocComment +import org.jetbrains.dokka.analysis.java.doccomment.DocCommentCreator +import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent +import org.jetbrains.dokka.analysis.java.util.PsiDocumentableSource +import org.jetbrains.dokka.analysis.java.util.from import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.JavaClassReference import org.jetbrains.dokka.links.Nullable import org.jetbrains.dokka.links.TypeConstructor import org.jetbrains.dokka.links.TypeReference -import org.jetbrains.dokka.model.AnnotationParameterValue -import org.jetbrains.dokka.model.AnnotationValue -import org.jetbrains.dokka.model.ArrayValue -import org.jetbrains.dokka.model.BooleanValue -import org.jetbrains.dokka.model.Callable -import org.jetbrains.dokka.model.ClassValue -import org.jetbrains.dokka.model.DClasslike -import org.jetbrains.dokka.model.DParameter -import org.jetbrains.dokka.model.DTypeAlias -import org.jetbrains.dokka.model.Documentable -import org.jetbrains.dokka.model.DocumentableSource -import org.jetbrains.dokka.model.DoubleValue -import org.jetbrains.dokka.model.EnumValue -import org.jetbrains.dokka.model.FloatValue -import org.jetbrains.dokka.model.IntValue -import org.jetbrains.dokka.model.LiteralValue -import org.jetbrains.dokka.model.LongValue -import org.jetbrains.dokka.model.NullValue -import org.jetbrains.dokka.model.StringValue +import org.jetbrains.dokka.model.* import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.idea.base.utils.fqname.getKotlinFqName -import org.jetbrains.kotlin.idea.kdoc.findKDoc -import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor -import org.jetbrains.kotlin.idea.util.hasComments -import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi +import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag +import org.jetbrains.kotlin.kdoc.psi.api.KDoc +import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtConstructor import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtDeclarationWithBody import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtPrimaryConstructor +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtTypeParameter +import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType +import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter import org.jetbrains.kotlin.renderer.render -import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.ImportPath +import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly +import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.safeAs @@ -77,16 +82,6 @@ fun Documentable.isLinkableElement(): Boolean = this is DParameter || this is DTypeAlias // TODO: issue #12: will not be included in DocumentableWithSources since it has no source -sealed interface DocComment { - fun hasTag(tag: JavadocTag): Boolean - - fun hasTagWithExceptionOfType(tag: JavadocTag, exceptionFqName: String): Boolean - - fun tagsByName(tag: JavadocTag, param: String? = null): List - - val tagNames: List -} - /** * Get Kdoc content. Note! This doesn't include the @ Kdoc tags. * @@ -97,7 +92,8 @@ val DocComment.documentString: String? get() = when (this) { is JavaDocComment -> comment.text is KotlinDocComment -> comment.text - }.getDocContentOrNull() + else -> null + }?.getDocContentOrNull() /** * Get text range of Kdoc/JavaDoc comment from /** to */ @@ -109,15 +105,20 @@ val DocComment.textRange: TextRange get() = when (this) { is JavaDocComment -> comment.textRange is KotlinDocComment -> comment.parent.textRange + else -> error("") } /** * Gets the [PsiNamedElement] from the [DocumentableSource] if it can find it. */ -val DocumentableSource.psi: PsiNamedElement? - get() = when (this) { - is PsiDocumentableSource -> psi - is DescriptorDocumentableSource -> descriptor.findPsi() as PsiNamedElement? +val DocumentableSource.psi: PsiElement? + get() = when (this::class.qualifiedName) { + PsiDocumentableSource::class.qualifiedName -> (this as PsiDocumentableSource).psi + "org.jetbrains.dokka.analysis.kotlin.symbols.services.KtPsiDocumentableSource" -> + Class.forName("org.jetbrains.dokka.analysis.kotlin.symbols.services.KtPsiDocumentableSource") + .getMethod("getPsi") + .invoke(this) as PsiElement? + else -> null } @@ -127,111 +128,220 @@ val DocumentableSource.psi: PsiNamedElement? val DocumentableSource.textRange: TextRange? get() = psi?.textRange -data class JavaDocComment(val comment: PsiDocComment) : DocComment { - override fun hasTag(tag: JavadocTag): Boolean = comment.hasTag(tag) +internal class JavaDocComment(val comment: PsiDocComment) : DocComment { + override fun hasTag(tag: JavadocTag): Boolean { + return when (tag) { + is ThrowingExceptionJavadocTag -> hasTag(tag) + else -> comment.hasTag(tag) + } + } + + private fun hasTag(tag: ThrowingExceptionJavadocTag): Boolean = + comment.hasTag(tag) && comment.resolveTag(tag).firstIsInstanceOrNull() + ?.resolveToElement() + ?.getKotlinFqName() == tag.exceptionQualifiedName - override fun hasTagWithExceptionOfType(tag: JavadocTag, exceptionFqName: String): Boolean = - comment.hasTag(tag) && - comment - .tagsByName(tag) - .firstIsInstanceOrNull() - ?.resolveToElement() - ?.getKotlinFqName() - ?.asString() == exceptionFqName + override fun resolveTag(tag: JavadocTag): List { + return when (tag) { + is ParamJavadocTag -> resolveParamTag(tag) + is ThrowingExceptionJavadocTag -> resolveThrowingTag(tag) + else -> comment.resolveTag(tag).map { PsiDocumentationContent(it, tag) } + } + } + + private fun resolveParamTag(tag: ParamJavadocTag): List { + val resolvedParamElements = comment.resolveTag(tag) + .filterIsInstance() + .map { it.contentElementsWithSiblingIfNeeded() } + .firstOrNull { it.firstOrNull()?.text == tag.paramName }.orEmpty() + + return resolvedParamElements + .withoutReferenceLink() + .map { PsiDocumentationContent(it, tag) } + } + + private fun resolveThrowingTag(tag: ThrowingExceptionJavadocTag): List { + val resolvedElements = comment.resolveTag(tag) + .flatMap { + when (it) { + is PsiDocTag -> it.contentElementsWithSiblingIfNeeded() + else -> listOf(it) + } + } + + return resolvedElements + .withoutReferenceLink() + .map { PsiDocumentationContent(it, tag) } + } + + private fun PsiDocComment.resolveTag(tag: JavadocTag): List { + return when (tag) { + DescriptionJavadocTag -> this.descriptionElements.toList() + else -> this.findTagsByName(tag.name).toList() + } + } - override fun tagsByName(tag: JavadocTag, param: String?): List = - comment.tagsByName(tag).map { PsiDocumentationContent(it, tag) } + private fun List.withoutReferenceLink(): List = drop(1) - override val tagNames: List - get() = comment.tags.map { it.name } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as JavaDocComment + + if (comment != other.comment) return false + + return true + } + + override fun hashCode(): Int { + return comment.hashCode() + } } -data class KotlinDocComment(val comment: KDocTag, val descriptor: DeclarationDescriptor?) : DocComment { +class KotlinDocComment( + val comment: KDocTag, + val resolveDocContext: ResolveDocContext +) : DocComment { - override fun hasTag(tag: JavadocTag): Boolean = - when (tag) { - JavadocTag.DESCRIPTION -> comment.getContent().isNotEmpty() - else -> tagsWithContent.any { it.text.startsWith("@$tag") } + private val tagsWithContent: List = comment.children.mapNotNull { (it as? KDocTag) } + + override fun hasTag(tag: JavadocTag): Boolean { + return when (tag) { + is DescriptionJavadocTag -> comment.getContent().isNotEmpty() + is ThrowingExceptionJavadocTag -> tagsWithContent.any { it.hasException(tag) } + else -> tagsWithContent.any { it.text.startsWith("@${tag.name}") } } + } - override fun hasTagWithExceptionOfType(tag: JavadocTag, exceptionFqName: String): Boolean = - tagsWithContent.any { it.hasExceptionWithName(tag, exceptionFqName) } + private fun KDocTag.hasException(tag: ThrowingExceptionJavadocTag) = + text.startsWith("@${tag.name}") && getSubjectName() == tag.exceptionQualifiedName - override fun tagsByName(tag: JavadocTag, param: String?): List = - when (tag) { - JavadocTag.DESCRIPTION -> listOf(DescriptorDocumentationContent(descriptor, comment, tag)) + override fun resolveTag(tag: JavadocTag): List { + return when (tag) { + is DescriptionJavadocTag -> listOf(DescriptorDocumentationContent(resolveDocContext, comment, tag)) + is ParamJavadocTag -> { + val resolvedContent = resolveGeneric(tag) + listOf(resolvedContent[tag.paramIndex]) + } - else -> - comment.children - .mapNotNull { (it as? KDocTag) } - .filter { it.name == "$tag" && param?.let { param -> it.hasExceptionWithName(param) } != false } - .map { DescriptorDocumentationContent(descriptor, it, tag) } + is ThrowsJavadocTag -> resolveThrowingException(tag) + is ExceptionJavadocTag -> resolveThrowingException(tag) + else -> resolveGeneric(tag) } + } - override val tagNames: List - get() = tagsWithContent.mapNotNull { it.name } + private fun resolveThrowingException(tag: ThrowingExceptionJavadocTag): List { + val exceptionName = tag.exceptionQualifiedName ?: return resolveGeneric(tag) - private val tagsWithContent: List = comment.children.mapNotNull { (it as? KDocTag) } + return comment.children + .filterIsInstance() + .filter { it.name == tag.name && it.getSubjectName() == exceptionName } + .map { DescriptorDocumentationContent(resolveDocContext, it, tag) } + } + + private fun resolveGeneric(tag: JavadocTag): List { + return comment.children.mapNotNull { element -> + if (element is KDocTag && element.name == tag.name) { + DescriptorDocumentationContent(resolveDocContext, element, tag) + } else { + null + } + } + } - private fun KDocTag.hasExceptionWithName(tag: JavadocTag, exceptionFqName: String) = - text.startsWith("@$tag") && hasExceptionWithName(exceptionFqName) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false - private fun KDocTag.hasExceptionWithName(exceptionFqName: String) = getSubjectName() == exceptionFqName + other as KotlinDocComment + + if (comment != other.comment) return false + //if (resolveDocContext.name != other.resolveDocContext.name) return false + if (tagsWithContent != other.tagsWithContent) return false + + return true + } + + override fun hashCode(): Int { + var result = comment.hashCode() + // result = 31 * result + resolveDocContext.name.hashCode() + result = 31 * result + tagsWithContent.hashCode() + return result + } } interface DocumentationContent { val tag: JavadocTag } -data class PsiDocumentationContent(val psiElement: PsiElement, override val tag: JavadocTag) : DocumentationContent +data class PsiDocumentationContent( + val psiElement: PsiElement, + override val tag: JavadocTag +) : DocumentationContent { + + override fun resolveSiblings(): List { + return if (psiElement is PsiDocTag) { + psiElement.contentElementsWithSiblingIfNeeded() + .map { content -> PsiDocumentationContent(content, tag) } + } else { + listOf(this) + } + } +} + +fun PsiDocTag.contentElementsWithSiblingIfNeeded(): List = if (dataElements.isNotEmpty()) { + listOfNotNull( + dataElements[0], + dataElements[0].nextSibling?.takeIf { it.text != dataElements.drop(1).firstOrNull()?.text }, + *dataElements.drop(1).toTypedArray() + ) +} else { + emptyList() +} + +class ResolveDocContext(val ktElement: KtElement) data class DescriptorDocumentationContent( - val descriptor: DeclarationDescriptor?, + val resolveDocContext: ResolveDocContext, val element: KDocTag, override val tag: JavadocTag, -) : DocumentationContent +) : DocumentationContent { + override fun resolveSiblings(): List { + return listOf(this) + } +} -fun PsiDocComment.hasTag(tag: JavadocTag): Boolean = +internal fun PsiDocComment.hasTag(tag: JavadocTag): Boolean = when (tag) { - JavadocTag.DESCRIPTION -> descriptionElements.isNotEmpty() - else -> findTagByName(tag.toString()) != null + DescriptionJavadocTag -> descriptionElements.isNotEmpty() + else -> findTagByName(tag.name) != null } fun PsiDocComment.tagsByName(tag: JavadocTag): List = when (tag) { - JavadocTag.DESCRIPTION -> descriptionElements.toList() + DescriptionJavadocTag -> descriptionElements.toList() else -> findTagsByName(tag.toString()).toList() } -enum class JavadocTag { - PARAM, - THROWS, - RETURN, - AUTHOR, - SEE, - DEPRECATED, - EXCEPTION, - HIDE, - INCLUDE, - - /** - * Artificial tag created to handle tag-less section - */ - DESCRIPTION, - ; - - override fun toString(): String = super.toString().lowercase() - - /* Missing tags: - SERIAL, - SERIAL_DATA, - SERIAL_FIELD, - SINCE, - VERSION - */ -} +internal fun PsiElement.getKotlinFqName(): String? = this.kotlinFqNameProp -fun PsiClass.implementsInterface(fqName: FqName): Boolean = allInterfaces().any { it.getKotlinFqName() == fqName } +//// from import org.jetbrains.kotlin.idea.base.psi.kotlinFqName +internal val PsiElement.kotlinFqNameProp: String? + get() = when (val element = this) { + is PsiPackage -> element.qualifiedName + is PsiClass -> element.qualifiedName + is PsiMember -> element.name?.let { name -> + val prefix = element.containingClass?.qualifiedName + if (prefix != null) "$prefix.$name" else name + } +// is KtNamedDeclaration -> element.fqName TODO [beresnev] decide what to do with it + is PsiQualifiedNamedElement -> element.qualifiedName + else -> null + } + +fun PsiClass.implementsInterface(fqName: FqName): Boolean = + allInterfaces().any { it.getKotlinFqName() == fqName.toString() } fun PsiClass.allInterfaces(): Sequence = sequence { @@ -244,6 +354,7 @@ fun PsiClass.allInterfaces(): Sequence = * This might be resolved once ultra light classes are enabled for dokka * See [KT-39518](https://youtrack.jetbrains.com/issue/KT-39518) */ +@OptIn(UnsafeCastFunction::class) fun PsiMethod.findSuperMethodsOrEmptyArray(logger: DokkaLogger): Array { return try { /* @@ -261,16 +372,109 @@ fun PsiMethod.findSuperMethodsOrEmptyArray(logger: DokkaLogger): Array +) + +internal fun KtElement.findKDoc(): KDocContent? = this.lookupOwnedKDoc() + ?: this.lookupKDocInContainer() + +/** + * Looks for sections that have a deeply nested [tag], + * as opposed to [KDoc.findSectionByTag], which only looks among the top level + */ +private fun KDoc.findSectionsContainingTag(tag: KDocKnownTag): List { + return getChildrenOfType() + .filter { it.findTagByName(tag.name.toLowerCaseAsciiOnly()) != null } +} + +private fun KtElement.lookupOwnedKDoc(): KDocContent? { + // KDoc for primary constructor is located inside of its class KDoc + val psiDeclaration = when (this) { + is KtPrimaryConstructor -> getContainingClassOrObject() + else -> this + } + + if (psiDeclaration is KtDeclaration) { + val kdoc = psiDeclaration.docComment + if (kdoc != null) { + if (this is KtConstructor<*>) { + // ConstructorDescriptor resolves to the same JetDeclaration + val constructorSection = kdoc.findSectionByTag(KDocKnownTag.CONSTRUCTOR) + if (constructorSection != null) { + // if annotated with @constructor tag and the caret is on constructor definition, + // then show @constructor description as the main content, and additional sections + // that contain @param tags (if any), as the most relatable ones + // practical example: val foo = Foo("argument") -- show @constructor and @param content + val paramSections = kdoc.findSectionsContainingTag(KDocKnownTag.PARAM) + return KDocContent(constructorSection, paramSections) + } + } + return KDocContent(kdoc.getDefaultSection(), kdoc.getAllSections()) + } + } + + return null +} + +private fun KtElement.lookupKDocInContainer(): KDocContent? { + val subjectName = name + val containingDeclaration = + PsiTreeUtil.findFirstParent(this, true) { + it is KtDeclarationWithBody && it !is KtPrimaryConstructor + || it is KtClassOrObject + } + + val containerKDoc = containingDeclaration?.getChildOfType() + if (containerKDoc == null || subjectName == null) return null + val propertySection = containerKDoc.findSectionByTag(KDocKnownTag.PROPERTY, subjectName) + val paramTag = + containerKDoc.findDescendantOfType { it.knownTag == KDocKnownTag.PARAM && it.getSubjectName() == subjectName } + + val primaryContent = when { + // class Foo(val s: String) + this is KtParameter && this.isPropertyParameter() -> propertySection ?: paramTag + // fun some(f: String) || class Some<T: Base> || Foo(s = "argument") + this is KtParameter || this is KtTypeParameter -> paramTag + // if this property is declared separately (outside primary constructor), but it's for some reason + // annotated as @property in class's description, instead of having its own KDoc + this is KtProperty && containingDeclaration is KtClassOrObject -> propertySection + else -> null + } + return primaryContent?.let { + // makes little sense to include any other sections, since we found + // documentation for a very specific element, like a property/param + KDocContent(it, sections = emptyList()) + } +} + +object DescriptorKotlinDocCommentCreator : DocCommentCreator { + override fun create(element: PsiNamedElement): DocComment? { + val ktElement = element.navigationElement as? KtElement ?: return null + val kdoc = ktElement.findKDoc() ?: return null + + return KotlinDocComment(kdoc.contentTag, ResolveDocContext(ktElement)) + } +} + +object JavaDocCommentCreator : DocCommentCreator { + override fun create(element: PsiNamedElement): DocComment? { + val psiDocComment = (element as? PsiDocCommentOwner)?.docComment ?: return null + return JavaDocComment(psiDocComment) + } +} + fun findClosestDocComment(element: PsiNamedElement?, logger: DokkaLogger): DocComment? { if (element == null) return null - (element as? PsiDocCommentOwner)?.docComment?.run { return@findClosestDocComment JavaDocComment(this) } - element.toKdocComment()?.run { return@findClosestDocComment this } + JavaDocCommentCreator.create(element)?.run { return this } + DescriptorKotlinDocCommentCreator.create(element)?.run { return this } if (element is PsiMethod) { val superMethods = element.findSuperMethodsOrEmptyArray(logger) @@ -309,21 +513,6 @@ fun findClosestDocComment(element: PsiNamedElement?, logger: DokkaLogger): DocCo return element.children.firstIsInstanceOrNull()?.let { JavaDocComment(it) } } -fun PsiNamedElement.toKdocComment(): KotlinDocComment? = - if (!hasComments()) { - null - } else { - (navigationElement as? KtElement)?.findKDoc { - // NOTE: This returns the wrong file if named the same and no comment was found! - DescriptorToSourceUtils.descriptorToDeclaration(it) - }?.run { - KotlinDocComment( - comment = this, - descriptor = (this@toKdocComment.navigationElement as? KtDeclaration)?.descriptor, - ) - } - } - fun PsiDocTag.resolveToElement(): PsiElement? = dataElements .firstOrNull() @@ -379,9 +568,9 @@ val ImportPath.hasStar: Boolean get() = isAllUnder val DocumentableSource.programmingLanguage: ProgrammingLanguage - get() = when (this) { - is PsiDocumentableSource -> ProgrammingLanguage.JAVA - is DescriptorDocumentableSource -> ProgrammingLanguage.KOTLIN + get() = when (this::class.qualifiedName) { + PsiDocumentableSource::class.qualifiedName -> ProgrammingLanguage.JAVA + "org.jetbrains.dokka.analysis.kotlin.symbols.services.KtPsiDocumentableSource" -> ProgrammingLanguage.KOTLIN else -> error("Unknown source type: ${this::class.simpleName}") } @@ -487,5 +676,4 @@ fun AnnotationParameterValue.getValue(): Any? = }, ) - else -> error("Could not read Annotation parameter: $this") } diff --git a/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/ProcessDocsAction.kt b/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/ProcessDocsAction.kt index d594eb78..f1912a70 100644 --- a/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/ProcessDocsAction.kt +++ b/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/ProcessDocsAction.kt @@ -2,7 +2,6 @@ package nl.jolanrensen.docProcessor import com.intellij.openapi.util.TextRange import mu.KotlinLogging -import nl.jolanrensen.docProcessor.ProcessDocsAction.Parameters import nl.jolanrensen.docProcessor.gradle.ProcessDocsGradleAction import nl.jolanrensen.docProcessor.gradle.lifecycle import org.jetbrains.dokka.CoreExtensions @@ -11,8 +10,6 @@ import org.jetbrains.dokka.DokkaConfigurationImpl import org.jetbrains.dokka.DokkaGenerator import org.jetbrains.dokka.DokkaSourceSetImpl import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.translators.descriptors.DefaultDescriptorToDocumentableTranslator -import org.jetbrains.dokka.base.translators.psi.DefaultPsiToDocumentableTranslator import org.jetbrains.dokka.model.WithSources import org.jetbrains.dokka.model.withDescendants import java.io.File @@ -123,18 +120,9 @@ abstract class ProcessDocsAction { // get the sourceToDocumentableTranslators from DokkaBase, both for java and kotlin files val context = dokkaGenerator.initializePlugins(configuration, logger, listOf(DokkaBase())) val translators = context[CoreExtensions.sourceToDocumentableTranslator] - .filter { - it is DefaultPsiToDocumentableTranslator || - // java - it is DefaultDescriptorToDocumentableTranslator // kotlin - } - - require(translators.any { it is DefaultPsiToDocumentableTranslator }) { - "Could not find DefaultPsiToDocumentableTranslator" - } - require(translators.any { it is DefaultDescriptorToDocumentableTranslator }) { - "Could not find DefaultDescriptorToDocumentableTranslator" + require(translators.isNotEmpty()) { + "Could not find any translators" } // execute the translators on the sources to gather the modules diff --git a/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/gradle/ProcessDocTask.kt b/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/gradle/ProcessDocTask.kt index 29168bd1..336a247f 100644 --- a/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/gradle/ProcessDocTask.kt +++ b/doc-processor-gradle-plugin/src/main/kotlin/nl/jolanrensen/docProcessor/gradle/ProcessDocTask.kt @@ -210,13 +210,12 @@ abstract class ProcessDocTask private fun Project.maybeCreateRuntimeConfiguration(): Configuration = project.configurations.maybeCreate("kotlinKdocIncludePluginRuntime") { isCanBeConsumed = true - val kotlinVersion = "1.8.10" - dependencies.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-compiler:$kotlinVersion")) - dependencies.add( - project.dependencies.create("org.jetbrains.dokka:dokka-analysis:$kotlinVersion"), - ) // compileOnly in base plugin - dependencies.add(project.dependencies.create("org.jetbrains.dokka:dokka-base:$kotlinVersion")) - dependencies.add(project.dependencies.create("org.jetbrains.dokka:dokka-core:$kotlinVersion")) + val dokkaVersion = "2.0.0-Beta" + + dependencies.add(project.dependencies.create("org.jetbrains.dokka:analysis-kotlin-api:$dokkaVersion")) + dependencies.add(project.dependencies.create("org.jetbrains.dokka:analysis-kotlin-symbols:$dokkaVersion")) + dependencies.add(project.dependencies.create("org.jetbrains.dokka:dokka-base:$dokkaVersion")) + dependencies.add(project.dependencies.create("org.jetbrains.dokka:dokka-core:$dokkaVersion")) } private fun NamedDomainObjectContainer.maybeCreate(name: String, configuration: T.() -> Unit): T =