From cf02a243237306930a7f6d149c43126699b33be6 Mon Sep 17 00:00:00 2001 From: Owen Gray Date: Wed, 27 Apr 2022 07:27:53 -0400 Subject: [PATCH 1/3] Retain source language information in Documentable tree This will allow dackka to figure out if a DParameter is from Kotlin or from Java Which is necessary to determine if a bare parameter is non-null K or platform J Test: added assertions to translator tests and the multilanguage linker test --- core/src/main/kotlin/model/Documentable.kt | 5 +- .../kotlin/model/documentableProperties.kt | 14 ++++ plugins/all-modules-page/out/index.md | 10 +++ ...faultDescriptorToDocumentableTranslator.kt | 64 +++++++++++----- .../documentables/DefaultPageCreator.kt | 4 +- .../documentables/documentableLanguage.kt | 11 +-- .../psi/DefaultPsiToDocumentableTranslator.kt | 76 ++++++++++++------- .../kotlin/linking/EnumValuesLinkingTest.kt | 13 +++- ...tDescriptorToDocumentableTranslatorTest.kt | 2 + .../DefaultPsiToDocumentableTranslatorTest.kt | 24 ++++-- 10 files changed, 155 insertions(+), 68 deletions(-) create mode 100644 plugins/all-modules-page/out/index.md diff --git a/core/src/main/kotlin/model/Documentable.kt b/core/src/main/kotlin/model/Documentable.kt index 0f73c8e1f1..09a2479683 100644 --- a/core/src/main/kotlin/model/Documentable.kt +++ b/core/src/main/kotlin/model/Documentable.kt @@ -7,9 +7,10 @@ import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.model.properties.WithExtraProperties interface AnnotationTarget +interface HasSourceLanguage abstract class Documentable : WithChildren, - AnnotationTarget { + AnnotationTarget, HasSourceLanguage { abstract val name: String? abstract val dri: DRI abstract val documentation: SourceSetDependent @@ -384,7 +385,7 @@ data class DTypeAlias( override fun withNewExtras(newExtras: PropertyContainer) = copy(extra = newExtras) } -sealed class Projection +sealed class Projection : HasSourceLanguage sealed class Bound : Projection() data class TypeParameter( val dri: DRI, diff --git a/core/src/main/kotlin/model/documentableProperties.kt b/core/src/main/kotlin/model/documentableProperties.kt index 87f40bd6ce..fa6c4697fb 100644 --- a/core/src/main/kotlin/model/documentableProperties.kt +++ b/core/src/main/kotlin/model/documentableProperties.kt @@ -46,3 +46,17 @@ data class CheckedExceptions(val exceptions: SourceSetDependent>) : Ex } override val key: ExtraProperty.Key = CheckedExceptions } + +enum class Language { + JAVA, KOTLIN, UNKNOWN +} + +data class SourceLanguage(val sourceLanguage: Language) : ExtraProperty { + companion object : ExtraProperty.Key { + override fun mergeStrategyFor(left: SourceLanguage, right: SourceLanguage) = MergeStrategy.Replace( + if (left.sourceLanguage == right.sourceLanguage) SourceLanguage(left.sourceLanguage) + else SourceLanguage(Language.UNKNOWN) + ) + } + override val key: ExtraProperty.Key = SourceLanguage +} diff --git a/plugins/all-modules-page/out/index.md b/plugins/all-modules-page/out/index.md new file mode 100644 index 0000000000..5c9593f62f --- /dev/null +++ b/plugins/all-modules-page/out/index.md @@ -0,0 +1,10 @@ +/ + +# Sample project + +Sample documentation with [external link](https://www.google.pl) + +## All modules: + +| Name | +|---| diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt index 6bc8774da0..2d95ba8ae0 100644 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt @@ -14,7 +14,6 @@ import org.jetbrains.dokka.analysis.from import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.parsers.MarkdownParser import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser -import org.jetbrains.dokka.base.translators.psi.DefaultPsiToDocumentableTranslator import org.jetbrains.dokka.base.translators.typeConstructorsBeingExceptions import org.jetbrains.dokka.base.translators.unquotedValue import org.jetbrains.dokka.links.* @@ -215,7 +214,8 @@ private class DokkaDescriptorVisitor( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), - info.ancestry.exceptionInSupertypesOrNull() + info.ancestry.exceptionInSupertypesOrNull(), + SourceLanguage(Language.KOTLIN) ) ) } @@ -253,7 +253,8 @@ private class DokkaDescriptorVisitor( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), - info.ancestry.exceptionInSupertypesOrNull() + info.ancestry.exceptionInSupertypesOrNull(), + SourceLanguage(Language.KOTLIN) ) ) } @@ -297,7 +298,8 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()) + ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), + SourceLanguage(Language.KOTLIN) ) ) } @@ -327,7 +329,8 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ConstructorValues(descriptor.getAppliedConstructorParameters().toSourceSetDependent()) + ConstructorValues(descriptor.getAppliedConstructorParameters().toSourceSetDependent()), + SourceLanguage(Language.KOTLIN) ) ) } @@ -361,7 +364,8 @@ private class DokkaDescriptorVisitor( isExpectActual = (isExpect || isActual), extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations() + descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), + SourceLanguage(Language.KOTLIN) ), companion = descriptor.companionObjectDescriptor?.let { objectDescriptor(it, driWithPlatform) }, visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), @@ -420,7 +424,8 @@ private class DokkaDescriptorVisitor( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), - info.ancestry.exceptionInSupertypesOrNull() + info.ancestry.exceptionInSupertypesOrNull(), + SourceLanguage(Language.KOTLIN) ) ) } @@ -469,6 +474,7 @@ private class DokkaDescriptorVisitor( .toAnnotations(), descriptor.getDefaultValue()?.let { DefaultValue(it) }, InheritedMember(inheritedFrom.toSourceSetDependent()), + SourceLanguage(Language.KOTLIN) ) ) ) @@ -520,6 +526,7 @@ private class DokkaDescriptorVisitor( (descriptor.getAnnotations() + descriptor.fileLevelAnnotations()).toSourceSetDependent() .toAnnotations(), ObviousMember.takeIf { descriptor.isObvious }, + SourceLanguage(Language.KOTLIN) ) ) } @@ -568,7 +575,8 @@ private class DokkaDescriptorVisitor( isExpectActual = (isExpect || isActual), extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations() + descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), + SourceLanguage(Language.KOTLIN) ).let { if (descriptor.isPrimary) { it + PrimaryConstructorExtra @@ -588,7 +596,10 @@ private class DokkaDescriptorVisitor( expectPresentInSet = null, documentation = descriptor.resolveDescriptorData(), sourceSets = setOf(sourceSet), - extra = PropertyContainer.withAll(descriptor.getAnnotations().toSourceSetDependent().toAnnotations()) + extra = PropertyContainer.withAll( + descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), + SourceLanguage(Language.KOTLIN) + ) ) private suspend fun visitPropertyAccessorDescriptor( @@ -611,7 +622,8 @@ private class DokkaDescriptorVisitor( sourceSets = setOf(sourceSet), extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - getAnnotationsWithBackingField().toSourceSetDependent().toAnnotations() + getAnnotationsWithBackingField().toSourceSetDependent().toAnnotations(), + SourceLanguage(Language.KOTLIN) ) ) @@ -669,7 +681,8 @@ private class DokkaDescriptorVisitor( isExpectActual = (isExpect || isActual), extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - descriptor.getAnnotations().toSourceSetDependent().toAnnotations() + descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), + SourceLanguage(Language.KOTLIN) ) ) } @@ -696,6 +709,7 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), info.exceptionInSupertypesOrNull(), + SourceLanguage(Language.KOTLIN) ) ) } @@ -712,7 +726,8 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll(listOfNotNull( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - descriptor.getDefaultValue()?.let { DefaultValue(it) } + descriptor.getDefaultValue()?.let { DefaultValue(it) }, + SourceLanguage(Language.KOTLIN) )) ) @@ -774,7 +789,10 @@ private class DokkaDescriptorVisitor( GenericTypeConstructor( DRI.from(kt.constructor.declarationDescriptor as DeclarationDescriptor), kt.arguments.map { it.toProjection() }, - extra = PropertyContainer.withAll(kt.getAnnotations().toSourceSetDependent().toAnnotations()) + extra = PropertyContainer.withAll( + kt.getAnnotations().toSourceSetDependent().toAnnotations(), + SourceLanguage(Language.KOTLIN) + ) ) private suspend fun buildAncestryInformation( @@ -818,7 +836,8 @@ private class DokkaDescriptorVisitor( setOf(sourceSet), extra = PropertyContainer.withAll( additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - getAnnotations().toSourceSetDependent().toAnnotations() + getAnnotations().toSourceSetDependent().toAnnotations(), + SourceLanguage(Language.KOTLIN) ) ) @@ -827,24 +846,27 @@ private class DokkaDescriptorVisitor( .safeAs()?.value?.let { unquotedValue(it) } private suspend fun KotlinType.toBound(): Bound { - suspend fun annotations(): PropertyContainer = + suspend fun extras(): PropertyContainer where T:AnnotationTarget, T:HasSourceLanguage = getAnnotations().takeIf { it.isNotEmpty() }?.let { annotations -> - PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations()) - } ?: PropertyContainer.empty() + PropertyContainer.withAll( + annotations.toSourceSetDependent().toAnnotations(), + SourceLanguage(Language.KOTLIN) + ) + } ?: PropertyContainer.withAll(SourceLanguage(Language.KOTLIN)) return when (this) { is DynamicType -> Dynamic is AbbreviatedType -> TypeAliased( abbreviation.toBound(), expandedType.toBound(), - annotations() + extras() ) else -> when (val ctor = constructor.declarationDescriptor) { is TypeParameterDescriptor -> TypeParameter( dri = DRI.from(ctor), name = ctor.name.asString(), presentableName = annotations.getPresentableName(), - extra = annotations() + extra = extras() ) is FunctionClassDescriptor -> FunctionalTypeConstructor( DRI.from(ctor), @@ -852,13 +874,13 @@ private class DokkaDescriptorVisitor( isExtensionFunction = isExtensionFunctionType || isBuiltinExtensionFunctionalType, isSuspendable = isSuspendFunctionTypeOrSubtype, presentableName = annotations.getPresentableName(), - extra = annotations() + extra = extras() ) else -> GenericTypeConstructor( DRI.from(ctor!!), // TODO: remove '!!' arguments.map { it.toProjection() }, annotations.getPresentableName(), - extra = annotations() + extra = extras() ) }.let { if (isMarkedNullable) Nullable(it) else it diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index c5136c27ca..776969f9fd 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -704,8 +704,8 @@ open class DefaultPageCreator( ) { (documentable as? WithSources)?.documentableLanguage(sourceSet)?.let { when (it) { - DocumentableLanguage.KOTLIN -> firstParagraphComment(tag.root) - DocumentableLanguage.JAVA -> firstSentenceComment(tag.root) + Language.KOTLIN -> firstParagraphComment(tag.root) + Language.JAVA -> firstSentenceComment(tag.root) } } ?: firstParagraphComment(tag.root) } diff --git a/plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt b/plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt index b3ce7c5c8a..c2962aac95 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt @@ -2,14 +2,11 @@ package org.jetbrains.dokka.base.translators.documentables import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.analysis.PsiDocumentableSource +import org.jetbrains.dokka.model.Language import org.jetbrains.dokka.model.WithSources -internal enum class DocumentableLanguage { - JAVA, KOTLIN -} - -internal fun WithSources.documentableLanguage(sourceSet: DokkaConfiguration.DokkaSourceSet): DocumentableLanguage = +internal fun WithSources.documentableLanguage(sourceSet: DokkaConfiguration.DokkaSourceSet): Language = when (sources[sourceSet]) { - is PsiDocumentableSource -> DocumentableLanguage.JAVA - else -> DocumentableLanguage.KOTLIN + is PsiDocumentableSource -> Language.JAVA + else -> Language.KOTLIN } \ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index f91991541b..f0110e49c2 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -1,5 +1,8 @@ package org.jetbrains.dokka.base.translators.psi +import com.intellij.ide.highlighter.JavaClassFileType +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.lang.jvm.JvmElement import com.intellij.lang.jvm.JvmModifier import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue @@ -25,7 +28,6 @@ import org.jetbrains.dokka.links.* import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.AnnotationTarget import org.jetbrains.dokka.model.Nullable -import org.jetbrains.dokka.model.TypeConstructor import org.jetbrains.dokka.model.doc.DocumentationNode import org.jetbrains.dokka.model.doc.Param import org.jetbrains.dokka.model.properties.PropertyContainer @@ -38,14 +40,18 @@ import org.jetbrains.dokka.utilities.parallelForEach import org.jetbrains.dokka.utilities.parallelMap import org.jetbrains.dokka.utilities.parallelMapNotNull import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation +import org.jetbrains.kotlin.asJava.elements.KtLightElement import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot +import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.psiUtil.getChildOfType +import org.jetbrains.kotlin.psi.psiUtil.isTopLevelKtOrJavaMember import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments import org.jetbrains.kotlin.utils.addToStdlib.safeAs @@ -213,7 +219,7 @@ class DefaultPsiToDocumentableTranslator( TypeParameter( dri = DRI.from(typeParameter), name = typeParameter.name.orEmpty(), - extra = typeParameter.annotations() + extra = typeParameter.extras() ) } ), @@ -268,7 +274,8 @@ class DefaultPsiToDocumentableTranslator( PropertyContainer.withAll( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() + .toAnnotations(), + getSourceLanguage() ) ) isEnum -> DEnum( @@ -287,7 +294,8 @@ class DefaultPsiToDocumentableTranslator( PropertyContainer.withAll( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() + .toAnnotations(), + psi.getSourceLanguage() ) ) }, @@ -306,7 +314,8 @@ class DefaultPsiToDocumentableTranslator( PropertyContainer.withAll( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() + .toAnnotations(), + psi.getSourceLanguage() ) ) isInterface -> DInterface( @@ -327,7 +336,8 @@ class DefaultPsiToDocumentableTranslator( PropertyContainer.withAll( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() + .toAnnotations(), + psi.getSourceLanguage() ) ) else -> DClass( @@ -351,7 +361,8 @@ class DefaultPsiToDocumentableTranslator( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() .toAnnotations(), - ancestry.exceptionInSupertypesOrNull() + ancestry.exceptionInSupertypesOrNull(), + psi.getSourceLanguage() ) ) } @@ -389,7 +400,8 @@ class DefaultPsiToDocumentableTranslator( setOf(sourceSetData), PropertyContainer.withAll( psiParameter.annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() + .toAnnotations(), + psi.getSourceLanguage() ) ) }, @@ -412,12 +424,19 @@ class DefaultPsiToDocumentableTranslator( .toAnnotations(), ObviousMember.takeIf { inheritedFrom != null && inheritedFrom.isObvious }, psi.throwsList.toDriList().takeIf { it.isNotEmpty() } - ?.let { CheckedExceptions(it.toSourceSetDependent()) } + ?.let { CheckedExceptions(it.toSourceSetDependent()) }, + psi.getSourceLanguage() ) } ) } + private fun PsiElement.getSourceLanguage() = when (this.containingFile.fileType) { + is KotlinFileType -> SourceLanguage(Language.KOTLIN) + is JavaFileType, is JavaClassFileType -> SourceLanguage(Language.JAVA) + else -> SourceLanguage(Language.UNKNOWN) + } + private fun PsiReferenceList.toDriList() = referenceElements.mapNotNull { it?.resolve()?.let { DRI.from(it) } } private fun PsiModifierListOwner.additionalExtras() = listOfNotNull( @@ -437,25 +456,28 @@ class DefaultPsiToDocumentableTranslator( Annotations.Annotation(DRI("kotlin.jvm", "JvmStatic"), emptyMap()) } - private fun PsiTypeParameter.annotations(): PropertyContainer = this.annotations.toList().toListOfAnnotations().annotations() - private fun PsiType.annotations(): PropertyContainer = this.annotations.toList().toListOfAnnotations().annotations() - - private fun List.annotations(): PropertyContainer = - this.takeIf { it.isNotEmpty() }?.let { annotations -> - PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations()) - } ?: PropertyContainer.empty() + private fun PsiTypeParameter.extras(): PropertyContainer where T : AnnotationTarget, T : HasSourceLanguage = + PropertyContainer.withAll( + this.annotations.toList().toListOfAnnotations().toSourceSetDependent().toAnnotations(), + this.containingFile.getSourceLanguage() + ) + private fun PsiType.extras(): PropertyContainer where T : AnnotationTarget, T : HasSourceLanguage = + PropertyContainer.withAll( + this.annotations.toList().toListOfAnnotations().toSourceSetDependent().toAnnotations(), + SourceLanguage(Language.JAVA) + ) private fun getBound(type: PsiType): Bound { fun bound() = when (type) { is PsiClassReferenceType -> type.resolve()?.let { resolved -> when { - resolved.qualifiedName == "java.lang.Object" -> JavaObject(type.annotations()) + resolved.qualifiedName == "java.lang.Object" -> JavaObject(type.extras()) resolved is PsiTypeParameter -> { TypeParameter( dri = DRI.from(resolved), name = resolved.name.orEmpty(), - extra = type.annotations() + extra = type.extras() ) } Regex("kotlin\\.jvm\\.functions\\.Function.*").matches(resolved.qualifiedName ?: "") || @@ -464,23 +486,23 @@ class DefaultPsiToDocumentableTranslator( ) -> FunctionalTypeConstructor( DRI.from(resolved), type.parameters.map { getProjection(it) }, - extra = type.annotations() + extra = type.extras() ) else -> GenericTypeConstructor( DRI.from(resolved), type.parameters.map { getProjection(it) }, - extra = type.annotations() + extra = type.extras() ) } - } ?: UnresolvedBound(type.presentableText, type.annotations()) + } ?: UnresolvedBound(type.presentableText, type.extras()) is PsiArrayType -> GenericTypeConstructor( DRI("kotlin", "Array"), listOf(getProjection(type.componentType)), - extra = type.annotations() + extra = type.extras() ) is PsiPrimitiveType -> if (type.name == "void") Void - else PrimitiveJavaType(type.name, type.annotations()) - is PsiImmediateClassType -> JavaObject(type.annotations()) + else PrimitiveJavaType(type.name, type.extras()) + is PsiImmediateClassType -> JavaObject(type.extras()) else -> throw IllegalStateException("${type.presentableText} is not supported by PSI parser") } @@ -530,7 +552,8 @@ class DefaultPsiToDocumentableTranslator( setOf(sourceSetData), PropertyContainer.withAll( type.annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() + .toAnnotations(), + getSourceLanguage() ) ) } @@ -582,7 +605,8 @@ class DefaultPsiToDocumentableTranslator( it.toSourceSetDependent().toAdditionalModifiers(), (psi.annotations.toList() .toListOfAnnotations() + it.toListOfAnnotations()).toSourceSetDependent() - .toAnnotations() + .toAnnotations(), + psi.getSourceLanguage() ) } ) diff --git a/plugins/base/src/test/kotlin/linking/EnumValuesLinkingTest.kt b/plugins/base/src/test/kotlin/linking/EnumValuesLinkingTest.kt index f95d9860ed..c5c0f33bec 100644 --- a/plugins/base/src/test/kotlin/linking/EnumValuesLinkingTest.kt +++ b/plugins/base/src/test/kotlin/linking/EnumValuesLinkingTest.kt @@ -3,7 +3,7 @@ package linking import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.links.DRIExtraContainer import org.jetbrains.dokka.links.EnumEntryDRIExtra -import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.doc.DocumentationLink import org.jetbrains.dokka.pages.ContentDRILink import org.jetbrains.dokka.pages.ContentPage @@ -37,7 +37,8 @@ class EnumValuesLinkingTest : BaseAbstractTest() { val classlikes = it.packages.single().children assertEquals(4, classlikes.size) - val javaLinker = classlikes.single { it.name == "JavaLinker" } + val javaLinker = classlikes.single { it.name == "JavaLinker" } as DClass + assertEquals(javaLinker.extra[SourceLanguage]!!.sourceLanguage, Language.JAVA) javaLinker.documentation.values.single().children.run { when (val kotlinLink = this[0].children[1].children[1]) { is DocumentationLink -> kotlinLink.dri.run { @@ -58,7 +59,8 @@ class EnumValuesLinkingTest : BaseAbstractTest() { } } - val kotlinLinker = classlikes.single { it.name == "KotlinLinker" } + val kotlinLinker = classlikes.single { it.name == "KotlinLinker" } as DClass + assertEquals(kotlinLinker.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) kotlinLinker.documentation.values.single().children.run { when (val kotlinLink = this[0].children[0].children[5]) { is DocumentationLink -> kotlinLink.dri.run { @@ -79,6 +81,11 @@ class EnumValuesLinkingTest : BaseAbstractTest() { } } + val kotlinEnum = classlikes.single { it.name == "KotlinEnum" } as DEnum + assertEquals(kotlinEnum.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) + val javaEnum = classlikes.single { it.name == "JavaEnum" } as DEnum + assertEquals(javaEnum.extra[SourceLanguage]!!.sourceLanguage, Language.JAVA) + assertEquals( javaLinker.documentation.values.single().children[0].children[1].children[1].safeAs()?.dri, kotlinLinker.documentation.values.single().children[0].children[0].children[5].safeAs()?.dri diff --git a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt index fd9fcc5d46..2b3e41fb3f 100644 --- a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt +++ b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt @@ -695,6 +695,7 @@ class DefaultDescriptorToDocumentableTranslatorTest : BaseAbstractTest() { Annotations.Annotation(DRI("sample", "Hello"), emptyMap()), type.extra[Annotations]?.directAnnotations?.values?.single()?.single() ) + assertEquals(type.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) } } } @@ -723,6 +724,7 @@ class DefaultDescriptorToDocumentableTranslatorTest : BaseAbstractTest() { type.extra[Annotations]?.directAnnotations?.values?.single()?.single() ) assertEquals("kotlin/Int///PointingToDeclaration/", type.dri.toString()) + assertEquals(type.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) } } } diff --git a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt index 9f74e2196f..0b84412015 100644 --- a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt +++ b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt @@ -3,10 +3,8 @@ package translators import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.Annotations -import org.jetbrains.dokka.model.TypeConstructor +import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.doc.Text -import org.jetbrains.dokka.model.firstMemberOfType import org.jetbrains.dokka.plugability.DokkaPlugin import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue @@ -240,12 +238,17 @@ class DefaultPsiToDocumentableTranslatorTest : BaseAbstractTest() { |} """.trimMargin(), configurationWithNoJVM, - pluginOverrides = listOf(OnlyPsiPlugin()) // suppress a descriptor translator because of psi and descriptor translators work in parallel + //pluginOverrides = listOf(OnlyPsiPlugin()) // suppress a descriptor translator because of psi and descriptor translators work in parallel ) { documentablesMergingStage = { module -> - val kotlinSubclassFunction = - module.packages.single().classlikes.find { it.name == "JavaLeafClass" }?.functions?.find { it.name == "kotlinSubclassFunction" } - .assertNotNull("kotlinSubclassFunction ") + val javaLeafClass = module.packages.single().classlikes.find { it.name == "JavaLeafClass" }!! as DClass + val kotlinSubClass = module.packages.single().classlikes.find { it.name == "KotlinSubClass" }!! as DClass + val kotlinLeafClassFunction = javaLeafClass.functions.find { it.name == "kotlinSubclassFunction" } + .assertNotNull("kotlinSubclassFunction") + val kotlinSubclassFunction = kotlinSubClass.functions.find { it.name == "kotlinSubclassFunction" } + .assertNotNull("kotlinSubclassFunction") + val javaLeafClassFunction = javaLeafClass.functions.find { it.name == "javaLeafClassFunction" } + .assertNotNull("javaLeafClassFunction") assertEquals( "String", @@ -255,6 +258,13 @@ class DefaultPsiToDocumentableTranslatorTest : BaseAbstractTest() { "String", (kotlinSubclassFunction.parameters.firstOrNull()?.type as? TypeConstructor)?.dri?.classNames ) + assertEquals(javaLeafClass.extra[SourceLanguage]!!.sourceLanguage, Language.JAVA) + assertEquals(kotlinSubClass.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) + assertEquals(kotlinSubclassFunction.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) + // This function was defined in Kotlin and inherited by a Java class without overriding. + // Its param types, if no nullability information is present, default to non-null instead of platform. + assertEquals(kotlinLeafClassFunction.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) + assertEquals(javaLeafClassFunction.extra[SourceLanguage]!!.sourceLanguage, Language.JAVA) } } } From 40010625325428f71c6a34fb3ea0cb6108bdf0ee Mon Sep 17 00:00:00 2001 From: Owen Gray Date: Fri, 13 May 2022 13:40:33 -0400 Subject: [PATCH 2/3] Switch to using WithSources; remove HasSourceLanguage Test: still needs dedicated tests --- core/src/main/kotlin/model/Documentable.kt | 92 +++-- .../kotlin/model/documentableProperties.kt | 9 - .../main/kotlin/model/documentableUtils.kt | 14 +- .../PreMergeDocumentableTransformer.kt | 1 + .../src/test/kotlin/model/DocumentableTest.kt | 16 +- .../jetbrains/dokka/analysis/Documentable.kt | 68 +++- .../signatures/KotlinSignatureProvider.kt | 7 +- .../DocumentableReplacerTransformer.kt | 2 +- ...DocumentableVisibilityFilterTransformer.kt | 28 +- ...linArrayDocumentableReplacerTransformer.kt | 44 +- ...faultDescriptorToDocumentableTranslator.kt | 210 +++++----- .../psi/DefaultPsiToDocumentableTranslator.kt | 385 +++++++++--------- .../KotlinArrayDocumentableReplacerTest.kt | 52 ++- .../kotlin/linking/EnumValuesLinkingTest.kt | 8 +- ...tDescriptorToDocumentableTranslatorTest.kt | 4 +- .../DefaultPsiToDocumentableTranslatorTest.kt | 10 +- .../translators/ExternalDocumentablesTest.kt | 11 +- .../converters/KotlinToJavaConverter.kt | 12 +- 18 files changed, 575 insertions(+), 398 deletions(-) diff --git a/core/src/main/kotlin/model/Documentable.kt b/core/src/main/kotlin/model/Documentable.kt index 09a2479683..579a7910e9 100644 --- a/core/src/main/kotlin/model/Documentable.kt +++ b/core/src/main/kotlin/model/Documentable.kt @@ -7,10 +7,9 @@ import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.model.properties.WithExtraProperties interface AnnotationTarget -interface HasSourceLanguage abstract class Documentable : WithChildren, - AnnotationTarget, HasSourceLanguage { + AnnotationTarget { abstract val name: String? abstract val dri: DRI abstract val documentation: SourceSetDependent @@ -193,9 +192,10 @@ data class DEnumEntry( override val functions: List, override val properties: List, override val classlikes: List, + override val sources: SourceSetDependent, override val sourceSets: Set, override val extra: PropertyContainer = PropertyContainer.empty() -) : Documentable(), WithScope, WithExtraProperties { +) : Documentable(), WithScope, WithExtraProperties, WithSources { override val children: List get() = (functions + properties + classlikes) @@ -322,9 +322,10 @@ data class DParameter( override val documentation: SourceSetDependent, override val expectPresentInSet: DokkaSourceSet?, override val type: Bound, + override val sources: SourceSetDependent, override val sourceSets: Set, override val extra: PropertyContainer = PropertyContainer.empty() -) : Documentable(), WithExtraProperties, WithType { +) : Documentable(), WithExtraProperties, WithType, WithSources { override val children: List get() = emptyList() @@ -336,9 +337,10 @@ data class DTypeParameter( override val documentation: SourceSetDependent, override val expectPresentInSet: DokkaSourceSet?, val bounds: List, + override val sources: SourceSetDependent, override val sourceSets: Set, override val extra: PropertyContainer = PropertyContainer.empty() -) : Documentable(), WithExtraProperties { +) : Documentable(), WithExtraProperties, WithSources { constructor( dri: DRI, @@ -347,13 +349,15 @@ data class DTypeParameter( documentation: SourceSetDependent, expectPresentInSet: DokkaSourceSet?, bounds: List, + sources: SourceSetDependent, sourceSets: Set, extra: PropertyContainer = PropertyContainer.empty() ) : this( - Invariance(TypeParameter(dri, name, presentableName)), + Invariance(TypeParameter(dri, name, presentableName, sources)), documentation, expectPresentInSet, bounds, + sources, sourceSets, extra ) @@ -375,38 +379,43 @@ data class DTypeAlias( override val visibility: SourceSetDependent, override val documentation: SourceSetDependent, override val expectPresentInSet: DokkaSourceSet?, + override val sources: SourceSetDependent, override val sourceSets: Set, override val generics: List, override val extra: PropertyContainer = PropertyContainer.empty() -) : Documentable(), WithType, WithVisibility, WithExtraProperties, WithGenerics { +) : Documentable(), WithType, WithVisibility, WithExtraProperties, WithGenerics, WithSources { override val children: List get() = emptyList() override fun withNewExtras(newExtras: PropertyContainer) = copy(extra = newExtras) } -sealed class Projection : HasSourceLanguage +sealed class Projection sealed class Bound : Projection() +val Bound.sources get() = (this as WithSources).sources data class TypeParameter( val dri: DRI, val name: String, val presentableName: String? = null, + override val sources: SourceSetDependent, override val extra: PropertyContainer = PropertyContainer.empty() -) : Bound(), AnnotationTarget, WithExtraProperties { +) : Bound(), AnnotationTarget, WithExtraProperties, WithSources { override fun withNewExtras(newExtras: PropertyContainer): TypeParameter = copy(extra = extra) } -sealed class TypeConstructor : Bound(), AnnotationTarget { +sealed class TypeConstructor : Bound(), AnnotationTarget, WithSources { abstract val dri: DRI abstract val projections: List abstract val presentableName: String? + abstract override val sources: SourceSetDependent } data class GenericTypeConstructor( override val dri: DRI, override val projections: List, override val presentableName: String? = null, + override val sources: SourceSetDependent, override val extra: PropertyContainer = PropertyContainer.empty() ) : TypeConstructor(), WithExtraProperties { override fun withNewExtras(newExtras: PropertyContainer): GenericTypeConstructor = @@ -419,6 +428,7 @@ data class FunctionalTypeConstructor( val isExtensionFunction: Boolean = false, val isSuspendable: Boolean = false, override val presentableName: String? = null, + override val sources: SourceSetDependent, override val extra: PropertyContainer = PropertyContainer.empty(), ) : TypeConstructor(), WithExtraProperties { override fun withNewExtras(newExtras: PropertyContainer): FunctionalTypeConstructor = @@ -429,39 +439,65 @@ data class FunctionalTypeConstructor( data class TypeAliased( val typeAlias: Bound, val inner: Bound, + override val sources: SourceSetDependent, override val extra: PropertyContainer = PropertyContainer.empty() -) : Bound(), AnnotationTarget, WithExtraProperties { +) : Bound(), AnnotationTarget, WithExtraProperties, WithSources { override fun withNewExtras(newExtras: PropertyContainer): TypeAliased = copy(extra = newExtras) } data class PrimitiveJavaType( val name: String, + override val sources: SourceSetDependent, override val extra: PropertyContainer = PropertyContainer.empty() -) : Bound(), AnnotationTarget, WithExtraProperties { +) : Bound(), AnnotationTarget, WithExtraProperties, WithSources { override fun withNewExtras(newExtras: PropertyContainer): PrimitiveJavaType = copy(extra = newExtras) } -data class JavaObject(override val extra: PropertyContainer = PropertyContainer.empty()) : - Bound(), AnnotationTarget, WithExtraProperties { +data class JavaObject( + override val sources: SourceSetDependent, + override val extra: PropertyContainer = PropertyContainer.empty() +) : + Bound(), AnnotationTarget, WithExtraProperties, WithSources { override fun withNewExtras(newExtras: PropertyContainer): JavaObject = copy(extra = newExtras) } data class UnresolvedBound( val name: String, + override val sources: SourceSetDependent, override val extra: PropertyContainer = PropertyContainer.empty() -) : Bound(), AnnotationTarget, WithExtraProperties { +) : Bound(), AnnotationTarget, WithExtraProperties, WithSources { override fun withNewExtras(newExtras: PropertyContainer): UnresolvedBound = copy(extra = newExtras) } // The following Projections are not AnnotationTargets; they cannot be annotated. -data class Nullable(val inner: Bound) : Bound() - -sealed class Variance : Projection() { +data class Nullable(val inner: Bound) : Bound(), WithSources { + override val sources: SourceSetDependent + get() = when (inner) { + is Nullable -> throw RuntimeException("Nullable Nullable is not a valid type") + is Dynamic -> throw RuntimeException("Nullable Dynamic is not a valid type") + is JavaObject -> inner.sources + is PrimitiveJavaType -> inner.sources + is TypeAliased -> inner.sources + is TypeConstructor -> inner.sources + is TypeParameter -> inner.sources + is UnresolvedBound -> inner.sources + is Void -> inner.sources + } +} + +sealed class Variance : Projection(), WithSources { abstract val inner: T + + override val sources: SourceSetDependent get() = when (inner) { + is WithSources -> (inner as WithSources).sources + is Dynamic -> TODO() + is Void -> TODO() + else -> throw RuntimeException("Impossible inner Bound for Variance: $inner") + } } data class Covariance(override val inner: T) : Variance() { @@ -476,15 +512,18 @@ data class Invariance(override val inner: T) : Variance() { override fun toString() = "" } +// Void cannot be an object because not all Void types are the same. For example, a Void declared in Java is of +// platform nullability, while a (non-wrapped) Void declared in Kotlin is non-null. +class Void(override val sources: SourceSetDependent) : Bound(), WithSources +// TODO: should these be WithSources? (that would require they be classes) +object Dynamic : Bound() object Star : Projection() -object Void : Bound() -object Dynamic : Bound() -fun Variance.withDri(dri: DRI) = when (this) { - is Contravariance -> Contravariance(TypeParameter(dri, inner.name, inner.presentableName)) - is Covariance -> Covariance(TypeParameter(dri, inner.name, inner.presentableName)) - is Invariance -> Invariance(TypeParameter(dri, inner.name, inner.presentableName)) +fun Variance.withDri(dri: DRI, sources: SourceSetDependent) = when (this) { + is Contravariance -> Contravariance(TypeParameter(dri, inner.name, inner.presentableName, sources)) + is Covariance -> Covariance(TypeParameter(dri, inner.name, inner.presentableName, sources)) + is Invariance -> Invariance(TypeParameter(dri, inner.name, inner.presentableName, sources)) } fun Documentable.dfs(predicate: (Documentable) -> Boolean): Documentable? = @@ -513,6 +552,11 @@ fun SourceSetDependent?.orEmpty(): SourceSetDependent = this ?: emptyM interface DocumentableSource { val path: String + val language: Language } data class TypeConstructorWithKind(val typeConstructor: TypeConstructor, val kind: ClassKind) + +public fun WithSources.sourceLanguage(sourceSet: DokkaSourceSet) = sources[sourceSet]!!.language + +public val WithSources.sourceLanguage: Language get() = this.sources.entries.single().value.language diff --git a/core/src/main/kotlin/model/documentableProperties.kt b/core/src/main/kotlin/model/documentableProperties.kt index fa6c4697fb..39350c6097 100644 --- a/core/src/main/kotlin/model/documentableProperties.kt +++ b/core/src/main/kotlin/model/documentableProperties.kt @@ -51,12 +51,3 @@ enum class Language { JAVA, KOTLIN, UNKNOWN } -data class SourceLanguage(val sourceLanguage: Language) : ExtraProperty { - companion object : ExtraProperty.Key { - override fun mergeStrategyFor(left: SourceLanguage, right: SourceLanguage) = MergeStrategy.Replace( - if (left.sourceLanguage == right.sourceLanguage) SourceLanguage(left.sourceLanguage) - else SourceLanguage(Language.UNKNOWN) - ) - } - override val key: ExtraProperty.Key = SourceLanguage -} diff --git a/core/src/main/kotlin/model/documentableUtils.kt b/core/src/main/kotlin/model/documentableUtils.kt index e32605ca17..b3fdd97154 100644 --- a/core/src/main/kotlin/model/documentableUtils.kt +++ b/core/src/main/kotlin/model/documentableUtils.kt @@ -9,13 +9,15 @@ fun DTypeParameter.filter(filteredSet: Set) = if (filteredSet.containsAll(sourceSets)) this else { val intersection = filteredSet.intersect(sourceSets) + val filteredSources = sources.filtered(intersection) if (intersection.isEmpty()) null else DTypeParameter( - variantTypeParameter, - documentation.filtered(intersection), - expectPresentInSet?.takeIf { intersection.contains(expectPresentInSet) }, - bounds, - intersection, - extra + variantTypeParameter = variantTypeParameter, + documentation = documentation.filtered(intersection), + expectPresentInSet = expectPresentInSet?.takeIf { intersection.contains(expectPresentInSet) }, + bounds = bounds, + sources = filteredSources, + sourceSets = intersection, + extra = extra ) } diff --git a/core/src/main/kotlin/transformers/documentation/PreMergeDocumentableTransformer.kt b/core/src/main/kotlin/transformers/documentation/PreMergeDocumentableTransformer.kt index 21548a4cfa..a4358fe8c5 100644 --- a/core/src/main/kotlin/transformers/documentation/PreMergeDocumentableTransformer.kt +++ b/core/src/main/kotlin/transformers/documentation/PreMergeDocumentableTransformer.kt @@ -34,3 +34,4 @@ fun PreMergeDocumentableTransformer.perPackageOptions(documentable: Documentable fun PreMergeDocumentableTransformer.source(documentable: T) where T : Documentable, T : WithSources = checkNotNull(documentable.sources[sourceSet(documentable)]) + { "Sources were null for ${sourceSet(documentable)} \nfor $documentable" } diff --git a/core/src/test/kotlin/model/DocumentableTest.kt b/core/src/test/kotlin/model/DocumentableTest.kt index 0652831c21..ebe7cac262 100644 --- a/core/src/test/kotlin/model/DocumentableTest.kt +++ b/core/src/test/kotlin/model/DocumentableTest.kt @@ -39,7 +39,7 @@ class DocumentableTest { modifier = emptyMap(), sources = emptyMap(), sourceSets = emptySet(), - type = Void, + type = Void(emptyMap()), receiver = null, isConstructor = false, isExpectActual = false, @@ -50,8 +50,9 @@ class DocumentableTest { documentation = emptyMap(), expectPresentInSet = null, extra = PropertyContainer.empty(), + sources = emptyMap(), sourceSets = emptySet(), - type = Void + type = Void(emptyMap()) ), DParameter( dri = DRI(), @@ -59,8 +60,9 @@ class DocumentableTest { documentation = emptyMap(), expectPresentInSet = null, extra = PropertyContainer.empty(), + sources = emptyMap(), sourceSets = emptySet(), - type = Void + type = Void(emptyMap()) ) ) ), @@ -75,7 +77,7 @@ class DocumentableTest { modifier = emptyMap(), sources = emptyMap(), sourceSets = emptySet(), - type = Void, + type = Void(emptyMap()), receiver = null, isConstructor = false, isExpectActual = false, @@ -87,7 +89,8 @@ class DocumentableTest { expectPresentInSet = null, extra = PropertyContainer.empty(), sourceSets = emptySet(), - type = Void + sources = emptyMap(), + type = Void(emptyMap()) ), DParameter( dri = DRI(), @@ -96,7 +99,8 @@ class DocumentableTest { expectPresentInSet = null, extra = PropertyContainer.empty(), sourceSets = emptySet(), - type = Void + sources = emptyMap(), + type = Void(emptyMap()) ) ) ) diff --git a/kotlin-analysis/src/main/kotlin/org/jetbrains/dokka/analysis/Documentable.kt b/kotlin-analysis/src/main/kotlin/org/jetbrains/dokka/analysis/Documentable.kt index 0c55fed43e..fcf5ba8c0b 100644 --- a/kotlin-analysis/src/main/kotlin/org/jetbrains/dokka/analysis/Documentable.kt +++ b/kotlin-analysis/src/main/kotlin/org/jetbrains/dokka/analysis/Documentable.kt @@ -1,14 +1,76 @@ package org.jetbrains.dokka.analysis +import com.intellij.ide.highlighter.JavaClassFileType +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.psi.PsiFile import com.intellij.psi.PsiNamedElement import org.jetbrains.dokka.model.DocumentableSource -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.dokka.model.Language +import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.SourceFile.NO_SOURCE_FILE +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.load.kotlin.toSourceElement +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.source.PsiSourceFile +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor -class DescriptorDocumentableSource(val descriptor: DeclarationDescriptor) : DocumentableSource { - override val path = descriptor.toSourceElement.containingFile.toString() +data class DescriptorDocumentableSource(val descriptor: DeclarationDescriptor) : DocumentableSource { + val file = descriptor.file() + val psi = descriptor.findPsi() + override val path = file.name!! + override val language = file.getSourceLanguage() + override fun toString() = "DescriptorDocumentableSource(path = $path, element = $psi)" } +/** The source file in which this declaration exists. Declarations can be contained inside others in the same file. */ +fun DeclarationDescriptor.file(): SourceFile = when { + this.source.name != null -> this.toSourceElement.containingFile + // FAKE_OVERRIDE is used in autogenerated functions, like some equals methods. The upstream signature is retained. + (this is DeserializedSimpleFunctionDescriptor && this.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) -> + this.overriddenDescriptors.first().file() + this is BuiltInsPackageFragment -> when { + this.fqName.asString().startsWith("kotlin") -> KOTLIN_BUILTINS // Inherited elements, e.g. enum.ordinal + this.fqName.asString().startsWith("java") -> JAVA_STDLIB + else -> throw RuntimeException("Unknown BuiltInsPackageFragment fqName: ${this.fqName.asString()}") + } + this is PackageFragmentDescriptor -> when { + this.fqName.asString().startsWith("kotlin") -> KOTLIN_BUILTINS // Mostly happens via external resolver + this.fqName.asString().startsWith("java") -> JAVA_STDLIB + else -> throw RuntimeException("Unknown BuiltInsPackageFragment fqName: ${this.fqName.asString()}") + } + + this.containingDeclaration != null -> this.containingDeclaration!!.file() + else -> SourceElement.NO_SOURCE.containingFile +} + +private val KOTLIN_BUILTINS = SourceFile { "KotlinBuiltins.kt" } +private val JAVA_STDLIB = SourceFile { "JavaStandardLibrary.java" } + +private val DeclarationDescriptor.source: SourceFile get() = this.toSourceElement.containingFile class PsiDocumentableSource(val psi: PsiNamedElement) : DocumentableSource { override val path = psi.containingFile.virtualFile.path + override val language: Language = psi.containingFile.getSourceLanguage() +} + +fun PsiFile.getSourceLanguage() = when (this.fileType) { + is KotlinFileType -> Language.KOTLIN + is JavaFileType, is JavaClassFileType -> Language.JAVA + else -> Language.UNKNOWN +} + +fun SourceFile.getSourceLanguage() = when (this) { + NO_SOURCE_FILE -> + throw RuntimeException("No source file present") // For testing + KOTLIN_BUILTINS -> Language.KOTLIN + JAVA_STDLIB -> Language.JAVA + //is JavaFileType, is JavaClassFileType -> Language.JAVA + is PsiSourceFile -> { + when (this.psiFile) { + is KtFile -> Language.KOTLIN + else -> throw RuntimeException("Uknown file type: ${this.psiFile}") + } + } + else -> throw RuntimeException("Uknown file type: $this") } diff --git a/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt b/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt index 3c9daa2942..9a524bade6 100644 --- a/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt +++ b/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt @@ -225,7 +225,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog * where `s` is both a function parameter and a property */ private fun DProperty.isAlsoParameter(sourceSet: DokkaSourceSet) = - (this.sources[sourceSet] as? DescriptorDocumentableSource)?.descriptor?.findPsi() is KtParameter + (this.sources[sourceSet] as? DescriptorDocumentableSource)?.psi is KtParameter private fun propertySignature(p: DProperty) = p.sourceSets.map { @@ -358,7 +358,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog private fun signature(t: DTypeParameter) = t.sourceSets.map { contentBuilder.contentFor(t, styles = t.stylesIfDeprecated(it), sourceSets = setOf(it)) { - signatureForProjection(t.variantTypeParameter.withDri(t.dri.withTargetToDeclaration())) + signatureForProjection(t.variantTypeParameter.withDri(t.dri.withTargetToDeclaration(), t.sources)) list(t.nontrivialBounds, prefix = " : ", surroundingCharactersStyle = mainStyles + TokenStyle.Operator) { bound -> signatureForProjection(bound) @@ -462,7 +462,8 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog private fun PrimitiveJavaType.translateToKotlin() = GenericTypeConstructor( dri = dri, projections = emptyList(), - presentableName = null + presentableName = null, + sources = sources ) private val DTypeParameter.nontrivialBounds: List diff --git a/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt index f5ef8ed152..11a12d54d6 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt @@ -219,7 +219,7 @@ abstract class DocumentableReplacerTransformer(val context: DokkaContext) : val wasChanged = underlyingType.any { it.value.changed } || generics.any { it.changed } return (dTypeAlias.takeIf { !wasChanged } ?: dTypeAlias.copy( underlyingType = underlyingType.mapValues { it.value.target ?: dTypeAlias.underlyingType.getValue(it.key) }, - generics = generics.mapNotNull { it.target } + generics = generics.mapNotNull { it.target }, )).let { AnyWithChanges(it, wasChanged) } } diff --git a/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilterTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilterTransformer.kt index 4b9da03bf7..3db4ac15ec 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilterTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableVisibilityFilterTransformer.kt @@ -215,7 +215,10 @@ class DocumentableVisibilityFilterTransformer(val context: DokkaContext) : PreMe } } - private fun filterEnumEntries(entries: List, filteredPlatforms: Set): Pair> = + private fun filterEnumEntries( + entries: List, + filteredPlatforms: Set, + ): Pair> = entries.foldRight(Pair(false, emptyList())) { entry, acc -> val intersection = filteredPlatforms.intersect(entry.sourceSets) if (intersection.isEmpty()) Pair(true, acc.second) @@ -225,15 +228,16 @@ class DocumentableVisibilityFilterTransformer(val context: DokkaContext) : PreMe val classlikes = filterClasslikes(entry.classlikes) { _, data -> data in intersection } DEnumEntry( - entry.dri, - entry.name, - entry.documentation.filtered(intersection), - entry.expectPresentInSet.filtered(filteredPlatforms), - functions.second, - properties.second, - classlikes.second, - intersection, - entry.extra + dri = entry.dri, + name = entry.name, + documentation = entry.documentation.filtered(intersection), + expectPresentInSet = entry.expectPresentInSet.filtered(filteredPlatforms), + functions = functions.second, + properties = properties.second, + classlikes = classlikes.second, + sources = entry.sources.filtered(filteredPlatforms), + sourceSets = intersection, + extra = entry.extra ).let { Pair(functions.first || properties.first || classlikes.first, acc.second + it) } } } @@ -361,8 +365,7 @@ class DocumentableVisibilityFilterTransformer(val context: DokkaContext) : PreMe private fun filterTypeAliases( typeAliases: List, additionalCondition: (DTypeAlias, DokkaSourceSet) -> Boolean = ::alwaysTrue - ) = - typeAliases.transform(additionalCondition) { original, filteredPlatforms -> + ) = typeAliases.transform(additionalCondition) { original, filteredPlatforms -> with(original) { copy( documentation = documentation.filtered(filteredPlatforms), @@ -370,6 +373,7 @@ class DocumentableVisibilityFilterTransformer(val context: DokkaContext) : PreMe underlyingType = underlyingType.filtered(filteredPlatforms), visibility = visibility.filtered(filteredPlatforms), generics = generics.mapNotNull { it.filter(filteredPlatforms) }, + sources = sources.filtered(filteredPlatforms), sourceSets = filteredPlatforms, ) } diff --git a/plugins/base/src/main/kotlin/transformers/documentables/KotlinArrayDocumentableReplacerTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/KotlinArrayDocumentableReplacerTransformer.kt index 251422f4d3..c03e4adbb6 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/KotlinArrayDocumentableReplacerTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/KotlinArrayDocumentableReplacerTransformer.kt @@ -19,35 +19,63 @@ class KotlinArrayDocumentableReplacerTransformer(context: DokkaContext): when (this?.dri) { DRI("kotlin", "Int") -> AnyWithChanges( - GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList()), + GenericTypeConstructor( + dri = DRI("kotlin", "IntArray"), + projections = emptyList(), + sources = genericTypeConstructor.sources + ), true) DRI("kotlin", "Boolean") -> AnyWithChanges( - GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()), + GenericTypeConstructor( + dri = DRI("kotlin", "BooleanArray"), + projections = emptyList(), + sources = genericTypeConstructor.sources + ), true) DRI("kotlin", "Float") -> AnyWithChanges( - GenericTypeConstructor(DRI("kotlin", "FloatArray"), emptyList()), + GenericTypeConstructor( + dri = DRI("kotlin", "FloatArray"), + projections = emptyList(), + sources = genericTypeConstructor.sources + ), true) DRI("kotlin", "Double") -> AnyWithChanges( - GenericTypeConstructor(DRI("kotlin", "DoubleArray"), emptyList()), + GenericTypeConstructor( + dri = DRI("kotlin", "DoubleArray"), + projections = emptyList(), + sources = genericTypeConstructor.sources + ), true) DRI("kotlin", "Long") -> AnyWithChanges( - GenericTypeConstructor(DRI("kotlin", "LongArray"), emptyList()), + GenericTypeConstructor( + dri = DRI("kotlin", "LongArray"), + projections = emptyList(), + sources = genericTypeConstructor.sources), true) DRI("kotlin", "Short") -> AnyWithChanges( - GenericTypeConstructor(DRI("kotlin", "ShortArray"), emptyList()), + GenericTypeConstructor( + dri = DRI("kotlin", "ShortArray"), + projections = emptyList(), + sources = genericTypeConstructor.sources), true) DRI("kotlin", "Char") -> AnyWithChanges( - GenericTypeConstructor(DRI("kotlin", "CharArray"), emptyList()), + GenericTypeConstructor( + dri = DRI("kotlin", "CharArray"), + projections = emptyList(), + sources = genericTypeConstructor.sources), true) DRI("kotlin", "Byte") -> AnyWithChanges( - GenericTypeConstructor(DRI("kotlin", "ByteArray"), emptyList()), + GenericTypeConstructor( + dri = DRI("kotlin", "ByteArray"), + projections = emptyList(), + sources = genericTypeConstructor.sources), true) else -> null } diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt index 2d95ba8ae0..3ab19d913b 100644 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt @@ -7,10 +7,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.analysis.DescriptorDocumentableSource -import org.jetbrains.dokka.analysis.DokkaResolutionFacade -import org.jetbrains.dokka.analysis.KotlinAnalysis -import org.jetbrains.dokka.analysis.from +import org.jetbrains.dokka.analysis.* import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.parsers.MarkdownParser import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser @@ -122,7 +119,7 @@ class DefaultDescriptorToDocumentableTranslator( data class DRIWithPlatformInfo( val dri: DRI, - val actual: SourceSetDependent + val actualSources: SourceSetDependent ) fun DRI.withEmptyInfo() = DRIWithPlatformInfo(this, emptyMap()) @@ -185,7 +182,7 @@ private class DokkaDescriptorVisitor( val scope = descriptor.unsubstitutedMemberScope val isExpect = descriptor.isExpect val isActual = descriptor.isActual - val info = descriptor.resolveClassDescriptionData() + val info = descriptor.resolveClassDescriptionData(parent.actualSources) return coroutineScope { val descriptorsWithKind = scope.getDescriptorsWithKind() @@ -193,7 +190,9 @@ private class DokkaDescriptorVisitor( val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) } val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) } val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) } - val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } } + val generics = async { descriptor.declaredTypeParameters.parallelMap { + it.toVariantTypeParameter(parent.actualSources) + } } DInterface( dri = driWithPlatform.dri, @@ -215,7 +214,6 @@ private class DokkaDescriptorVisitor( descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), info.ancestry.exceptionInSupertypesOrNull(), - SourceLanguage(Language.KOTLIN) ) ) } @@ -226,7 +224,8 @@ private class DokkaDescriptorVisitor( val scope = descriptor.unsubstitutedMemberScope val isExpect = descriptor.isExpect val isActual = descriptor.isActual - val info = descriptor.resolveClassDescriptionData() + val sources = descriptor.createSources() + val info = descriptor.resolveClassDescriptionData(sources) return coroutineScope { @@ -242,7 +241,7 @@ private class DokkaDescriptorVisitor( functions = functions.await(), properties = properties.await(), classlikes = classlikes.await(), - sources = descriptor.createSources(), + sources = sources, expectPresentInSet = sourceSet.takeIf { isExpect }, visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), supertypes = info.supertypes.toSourceSetDependent(), @@ -254,7 +253,6 @@ private class DokkaDescriptorVisitor( descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), info.ancestry.exceptionInSupertypesOrNull(), - SourceLanguage(Language.KOTLIN) ) ) } @@ -267,7 +265,8 @@ private class DokkaDescriptorVisitor( val scope = descriptor.unsubstitutedMemberScope val isExpect = descriptor.isExpect val isActual = descriptor.isActual - val info = descriptor.resolveClassDescriptionData() + val sources = descriptor.createSources() + val info = descriptor.resolveClassDescriptionData(sources) return coroutineScope { val descriptorsWithKind = scope.getDescriptorsWithKind() @@ -287,7 +286,7 @@ private class DokkaDescriptorVisitor( functions = functions.await(), properties = properties.await(), classlikes = classlikes.await(), - sources = descriptor.createSources(), + sources = sources, expectPresentInSet = sourceSet.takeIf { isExpect }, visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), supertypes = info.supertypes.toSourceSetDependent(), @@ -299,7 +298,6 @@ private class DokkaDescriptorVisitor( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), - SourceLanguage(Language.KOTLIN) ) ) } @@ -325,12 +323,12 @@ private class DokkaDescriptorVisitor( properties = properties.await(), classlikes = classlikes.await(), sourceSets = setOf(sourceSet), + sources = descriptor.createSources(), expectPresentInSet = sourceSet.takeIf { isExpect }, extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), ConstructorValues(descriptor.getAppliedConstructorParameters().toSourceSetDependent()), - SourceLanguage(Language.KOTLIN) ) ) } @@ -344,11 +342,14 @@ private class DokkaDescriptorVisitor( return coroutineScope { val descriptorsWithKind = scope.getDescriptorsWithKind() + val sources = descriptor.createSources() val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) } val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) } val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) } - val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } } + val generics = async { descriptor.declaredTypeParameters.parallelMap { + it.toVariantTypeParameter(sources) + } } val constructors = async { descriptor.constructors.parallelMap { visitConstructorDescriptor(it, driWithPlatform) } } @@ -365,13 +366,12 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - SourceLanguage(Language.KOTLIN) ), companion = descriptor.companionObjectDescriptor?.let { objectDescriptor(it, driWithPlatform) }, visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), generics = generics.await(), constructors = constructors.await(), - sources = descriptor.createSources() + sources = sources ) } @@ -381,10 +381,10 @@ private class DokkaDescriptorVisitor( private suspend fun classDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DClass { val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo() val scope = descriptor.unsubstitutedMemberScope + val actualSources = descriptor.createSources() val isExpect = descriptor.isExpect val isActual = descriptor.isActual - val info = descriptor.resolveClassDescriptionData() - val actual = descriptor.createSources() + val info = descriptor.resolveClassDescriptionData(actualSources) return coroutineScope { val descriptorsWithKind = scope.getDescriptorsWithKind() @@ -392,12 +392,14 @@ private class DokkaDescriptorVisitor( val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) } val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) } val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) } - val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } } + val generics = async { + descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter(actualSources) } + } val constructors = async { descriptor.constructors.parallelMap { visitConstructorDescriptor( it, - if (it.isPrimary) DRIWithPlatformInfo(driWithPlatform.dri, actual) + if (it.isPrimary) DRIWithPlatformInfo(driWithPlatform.dri, actualSources) else DRIWithPlatformInfo(driWithPlatform.dri, emptyMap()) ) } @@ -410,7 +412,7 @@ private class DokkaDescriptorVisitor( functions = functions.await(), properties = properties.await(), classlikes = classlikes.await(), - sources = actual, + sources = actualSources, expectPresentInSet = sourceSet.takeIf { isExpect }, visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), supertypes = info.supertypes.toSourceSetDependent(), @@ -425,7 +427,6 @@ private class DokkaDescriptorVisitor( descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), info.ancestry.exceptionInSupertypesOrNull(), - SourceLanguage(Language.KOTLIN) ) ) } @@ -440,18 +441,18 @@ private class DokkaDescriptorVisitor( val isExpect = descriptor.isExpect val isActual = descriptor.isActual - val actual = originalDescriptor.createSources() + val actualSources = originalDescriptor.createSources() return coroutineScope { - val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } } + val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter(actualSources) } } DProperty( dri = dri, name = descriptor.name.asString(), receiver = descriptor.extensionReceiverParameter?.let { - visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual)) + visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actualSources)) }, - sources = actual, + sources = actualSources, getter = descriptor.accessors.filterIsInstance().singleOrNull()?.let { visitPropertyAccessorDescriptor(it, descriptor, dri) }, @@ -461,7 +462,7 @@ private class DokkaDescriptorVisitor( visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), documentation = descriptor.resolveDescriptorData(), modifier = descriptor.modifier().toSourceSetDependent(), - type = descriptor.returnType!!.toBound(), + type = descriptor.returnType!!.toBound(actualSources), expectPresentInSet = sourceSet.takeIf { isExpect }, sourceSets = setOf(sourceSet), generics = generics.await(), @@ -474,7 +475,6 @@ private class DokkaDescriptorVisitor( .toAnnotations(), descriptor.getDefaultValue()?.let { DefaultValue(it) }, InheritedMember(inheritedFrom.toSourceSetDependent()), - SourceLanguage(Language.KOTLIN) ) ) ) @@ -496,28 +496,28 @@ private class DokkaDescriptorVisitor( val isExpect = descriptor.isExpect val isActual = descriptor.isActual - val actual = originalDescriptor.createSources() + val actualSources = originalDescriptor.createSources() return coroutineScope { - val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } } + val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter(actualSources) } } DFunction( dri = dri, name = descriptor.name.asString(), isConstructor = false, receiver = descriptor.extensionReceiverParameter?.let { - visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual)) + visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actualSources)) }, parameters = descriptor.valueParameters.mapIndexed { index, desc -> - parameter(index, desc, DRIWithPlatformInfo(dri, actual)) + parameter(index, desc, DRIWithPlatformInfo(dri, actualSources)) }, expectPresentInSet = sourceSet.takeIf { isExpect }, - sources = actual, + sources = actualSources, visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), generics = generics.await(), documentation = descriptor.takeIf { it.kind != CallableMemberDescriptor.Kind.SYNTHESIZED } ?.resolveDescriptorData() ?: emptyMap(), modifier = descriptor.modifier().toSourceSetDependent(), - type = descriptor.returnType!!.toBound(), + type = descriptor.returnType!!.toBound(actualSources), sourceSets = setOf(sourceSet), isExpectActual = (isExpect || isActual), extra = PropertyContainer.withAll( @@ -526,7 +526,6 @@ private class DokkaDescriptorVisitor( (descriptor.getAnnotations() + descriptor.fileLevelAnnotations()).toSourceSetDependent() .toAnnotations(), ObviousMember.takeIf { descriptor.isObvious }, - SourceLanguage(Language.KOTLIN) ) ) } @@ -535,24 +534,24 @@ private class DokkaDescriptorVisitor( suspend fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, parent: DRIWithPlatformInfo): DFunction { val name = descriptor.constructedClass.name.toString() val dri = parent.dri.copy(callable = Callable.from(descriptor, name)) - val actual = descriptor.createSources() + val actualSources = descriptor.createSources() val isExpect = descriptor.isExpect val isActual = descriptor.isActual return coroutineScope { - val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } } + val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter(actualSources) } } DFunction( dri = dri, name = name, isConstructor = true, receiver = descriptor.extensionReceiverParameter?.let { - visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual)) + visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actualSources)) }, parameters = descriptor.valueParameters.mapIndexed { index, desc -> - parameter(index, desc, DRIWithPlatformInfo(dri, actual)) + parameter(index, desc, DRIWithPlatformInfo(dri, actualSources)) }, - sources = actual, + sources = actualSources, expectPresentInSet = sourceSet.takeIf { isExpect }, visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), documentation = descriptor.resolveDescriptorData().let { sourceSetDependent -> @@ -568,7 +567,7 @@ private class DokkaDescriptorVisitor( sourceSetDependent } }, - type = descriptor.returnType.toBound(), + type = descriptor.returnType.toBound(actualSources), modifier = descriptor.modifier().toSourceSetDependent(), generics = generics.await(), sourceSets = setOf(sourceSet), @@ -576,7 +575,6 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - SourceLanguage(Language.KOTLIN) ).let { if (descriptor.isPrimary) { it + PrimaryConstructorExtra @@ -592,14 +590,12 @@ private class DokkaDescriptorVisitor( ) = DParameter( dri = parent.dri.copy(target = PointingToDeclaration), name = null, - type = descriptor.type.toBound(), + type = descriptor.type.toBound(parent.actualSources), expectPresentInSet = null, documentation = descriptor.resolveDescriptorData(), sourceSets = setOf(sourceSet), - extra = PropertyContainer.withAll( - descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - SourceLanguage(Language.KOTLIN) - ) + sources = parent.actualSources, + extra = PropertyContainer.withAll(descriptor.getAnnotations().toSourceSetDependent().toAnnotations()) ) private suspend fun visitPropertyAccessorDescriptor( @@ -611,19 +607,20 @@ private class DokkaDescriptorVisitor( val isGetter = descriptor is PropertyGetterDescriptor val isExpect = descriptor.isExpect val isActual = descriptor.isActual + val actualSources = descriptor.createSources() suspend fun PropertyDescriptor.asParameter(parent: DRI) = DParameter( parent.copy(target = PointingToCallableParameters(parameterIndex = 1)), this.name.asString(), - type = this.type.toBound(), + type = this.type.toBound(actualSources), expectPresentInSet = sourceSet.takeIf { isExpect }, documentation = descriptor.resolveDescriptorData(), sourceSets = setOf(sourceSet), + sources = actualSources, extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), getAnnotationsWithBackingField().toSourceSetDependent().toAnnotations(), - SourceLanguage(Language.KOTLIN) ) ) @@ -657,7 +654,7 @@ private class DokkaDescriptorVisitor( } return coroutineScope { - val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } } + val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter(actualSources) } } DFunction( dri, @@ -666,7 +663,7 @@ private class DokkaDescriptorVisitor( parameters = parameters, visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), documentation = descriptor.resolveDescriptorData(), - type = descriptor.returnType!!.toBound(), + type = descriptor.returnType!!.toBound(actualSources), generics = generics.await(), modifier = descriptor.modifier().toSourceSetDependent(), expectPresentInSet = sourceSet.takeIf { isExpect }, @@ -682,52 +679,59 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - SourceLanguage(Language.KOTLIN) ) ) } } - private suspend fun visitTypeAliasDescriptor(descriptor: TypeAliasDescriptor, parent: DRIWithPlatformInfo?) = + private suspend fun visitTypeAliasDescriptor(descriptor: TypeAliasDescriptor, parent: DRIWithPlatformInfo) = with(descriptor) { coroutineScope { - val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } } - val info = buildAncestryInformation(defaultType).copy( - superclass = buildAncestryInformation(underlyingType), + val generics = async { + descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter(parent.actualSources) } + } + val info = buildAncestryInformation(defaultType, parent.actualSources).copy( + superclass = buildAncestryInformation(underlyingType, parent.actualSources), interfaces = emptyList() ) + val sources = descriptor.createSources() DTypeAlias( dri = DRI.from(this@with), name = name.asString(), - type = defaultType.toBound(), + type = defaultType.toBound(sources), expectPresentInSet = null, - underlyingType = underlyingType.toBound().toSourceSetDependent(), + underlyingType = underlyingType.toBound(sources).toSourceSetDependent(), visibility = visibility.toDokkaVisibility().toSourceSetDependent(), documentation = resolveDescriptorData(), sourceSets = setOf(sourceSet), + // A TypeAlias in Kotlin of a Java type uses Kotlin properties like default nullability + sources = sources, generics = generics.await(), extra = PropertyContainer.withAll( descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), info.exceptionInSupertypesOrNull(), - SourceLanguage(Language.KOTLIN) ) ) } } - private suspend fun parameter(index: Int, descriptor: ValueParameterDescriptor, parent: DRIWithPlatformInfo) = - DParameter( + private suspend fun parameter( + index: Int, + descriptor: ValueParameterDescriptor, + parent: DRIWithPlatformInfo + ) = DParameter( dri = parent.dri.copy(target = PointingToCallableParameters(index)), name = descriptor.name.asString(), - type = descriptor.varargElementType?.toBound() ?: descriptor.type.toBound(), + type = descriptor.varargElementType?.toBound(parent.actualSources) + ?: descriptor.type.toBound(parent.actualSources), expectPresentInSet = null, documentation = descriptor.resolveDescriptorData(), sourceSets = setOf(sourceSet), + sources = parent.actualSources, extra = PropertyContainer.withAll(listOfNotNull( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), descriptor.getDefaultValue()?.let { DefaultValue(it) }, - SourceLanguage(Language.KOTLIN) )) ) @@ -785,18 +789,17 @@ private class DokkaDescriptorVisitor( getDocumentation()?.toSourceSetDependent() ?: emptyMap() - private suspend fun toTypeConstructor(kt: KotlinType) = + private suspend fun toTypeConstructor(kt: KotlinType, sources: SourceSetDependent) = GenericTypeConstructor( DRI.from(kt.constructor.declarationDescriptor as DeclarationDescriptor), - kt.arguments.map { it.toProjection() }, - extra = PropertyContainer.withAll( - kt.getAnnotations().toSourceSetDependent().toAnnotations(), - SourceLanguage(Language.KOTLIN) - ) + kt.arguments.map { it.toProjection(sources) }, + sources = sources, + extra = PropertyContainer.withAll(kt.getAnnotations().toSourceSetDependent().toAnnotations()) ) private suspend fun buildAncestryInformation( - kotlinType: KotlinType + kotlinType: KotlinType, + sources: SourceSetDependent ): AncestryNode { val (interfaces, superclass) = kotlinType.immediateSupertypes().filterNot { it.isAnyOrNullableAny() } .partition { @@ -808,36 +811,41 @@ private class DokkaDescriptorVisitor( return coroutineScope { AncestryNode( - typeConstructor = toTypeConstructor(kotlinType), - superclass = superclass.parallelMap(::buildAncestryInformation).singleOrNull(), - interfaces = interfaces.parallelMap(::buildAncestryInformation) + typeConstructor = toTypeConstructor(kotlinType, sources), + superclass = superclass.parallelMap { buildAncestryInformation(it, sources) }.singleOrNull(), + interfaces = interfaces.parallelMap { buildAncestryInformation(it, sources) } ) } } - private suspend fun ClassDescriptor.resolveClassDescriptionData(): ClassInfo { + private suspend fun ClassDescriptor.resolveClassDescriptionData(sources: SourceSetDependent): ClassInfo { return coroutineScope { ClassInfo( - buildAncestryInformation(this@resolveClassDescriptionData.defaultType), + buildAncestryInformation(this@resolveClassDescriptionData.defaultType, sources), resolveDescriptorData() ) } } - private suspend fun TypeParameterDescriptor.toVariantTypeParameter() = + private suspend fun TypeParameterDescriptor.toVariantTypeParameter(sources: SourceSetDependent) = DTypeParameter( variantTypeParameter( - TypeParameter(DRI.from(this), name.identifier, annotations.getPresentableName()) + TypeParameter( + DRI.from(this), + name.identifier, + annotations.getPresentableName(), + sources + ) ), resolveDescriptorData(), null, - upperBounds.map { it.toBound() }, + upperBounds.map { it.toBound(sources) }, + sources, setOf(sourceSet), extra = PropertyContainer.withAll( additionalExtras().toSourceSetDependent().toAdditionalModifiers(), getAnnotations().toSourceSetDependent().toAnnotations(), - SourceLanguage(Language.KOTLIN) ) ) @@ -845,41 +853,43 @@ private class DokkaDescriptorVisitor( mapNotNull { it.toAnnotation() }.singleOrNull { it.dri.classNames == "ParameterName" }?.params?.get("name") .safeAs()?.value?.let { unquotedValue(it) } - private suspend fun KotlinType.toBound(): Bound { - suspend fun extras(): PropertyContainer where T:AnnotationTarget, T:HasSourceLanguage = + private suspend fun KotlinType.toBound(sources: SourceSetDependent): Bound { + + suspend fun extras(): PropertyContainer where T:AnnotationTarget = getAnnotations().takeIf { it.isNotEmpty() }?.let { annotations -> - PropertyContainer.withAll( - annotations.toSourceSetDependent().toAnnotations(), - SourceLanguage(Language.KOTLIN) - ) - } ?: PropertyContainer.withAll(SourceLanguage(Language.KOTLIN)) + PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations()) + } ?: PropertyContainer.empty() return when (this) { is DynamicType -> Dynamic is AbbreviatedType -> TypeAliased( - abbreviation.toBound(), - expandedType.toBound(), - extras() + typeAlias = abbreviation.toBound(sources), + inner = expandedType.toBound(sources), + sources = sources, + extra = extras() ) else -> when (val ctor = constructor.declarationDescriptor) { is TypeParameterDescriptor -> TypeParameter( dri = DRI.from(ctor), name = ctor.name.asString(), presentableName = annotations.getPresentableName(), + sources = sources, extra = extras() ) is FunctionClassDescriptor -> FunctionalTypeConstructor( - DRI.from(ctor), - arguments.map { it.toProjection() }, + dri = DRI.from(ctor), + projections = arguments.map { it.toProjection(sources) }, isExtensionFunction = isExtensionFunctionType || isBuiltinExtensionFunctionalType, isSuspendable = isSuspendFunctionTypeOrSubtype, presentableName = annotations.getPresentableName(), + sources = sources, extra = extras() ) else -> GenericTypeConstructor( - DRI.from(ctor!!), // TODO: remove '!!' - arguments.map { it.toProjection() }, - annotations.getPresentableName(), + dri = DRI.from(ctor!!), // TODO: remove '!!' + projections = arguments.map { it.toProjection(sources) }, + presentableName = annotations.getPresentableName(), + sources = sources, extra = extras() ) }.let { @@ -888,11 +898,11 @@ private class DokkaDescriptorVisitor( } } - private suspend fun TypeProjection.toProjection(): Projection = - if (isStarProjection) Star else formPossiblyVariant() + private suspend fun TypeProjection.toProjection(sources: SourceSetDependent): Projection = + if (isStarProjection) Star else formPossiblyVariant(sources) - private suspend fun TypeProjection.formPossiblyVariant(): Projection = - type.toBound().wrapWithVariance(projectionKind) + private suspend fun TypeProjection.formPossiblyVariant(sources: SourceSetDependent) = + type.toBound(sources).wrapWithVariance(projectionKind) private fun TypeParameterDescriptor.variantTypeParameter(type: TypeParameter) = type.wrapWithVariance(variance) diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index f0110e49c2..8a00d15104 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -1,8 +1,5 @@ package org.jetbrains.dokka.base.translators.psi -import com.intellij.ide.highlighter.JavaClassFileType -import com.intellij.ide.highlighter.JavaFileType -import com.intellij.lang.jvm.JvmElement import com.intellij.lang.jvm.JvmModifier import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue @@ -15,10 +12,7 @@ import com.intellij.psi.impl.source.PsiImmediateClassType import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.analysis.DokkaResolutionFacade -import org.jetbrains.dokka.analysis.KotlinAnalysis -import org.jetbrains.dokka.analysis.PsiDocumentableSource -import org.jetbrains.dokka.analysis.from +import org.jetbrains.dokka.analysis.* import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.translators.typeConstructorsBeingExceptions import org.jetbrains.dokka.base.translators.psi.parsers.JavaDocumentationParser @@ -26,7 +20,6 @@ import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser import org.jetbrains.dokka.base.translators.unquotedValue import org.jetbrains.dokka.links.* import org.jetbrains.dokka.model.* -import org.jetbrains.dokka.model.AnnotationTarget import org.jetbrains.dokka.model.Nullable import org.jetbrains.dokka.model.doc.DocumentationNode import org.jetbrains.dokka.model.doc.Param @@ -40,18 +33,14 @@ import org.jetbrains.dokka.utilities.parallelForEach import org.jetbrains.dokka.utilities.parallelMap import org.jetbrains.dokka.utilities.parallelMapNotNull import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation -import org.jetbrains.kotlin.asJava.elements.KtLightElement import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot -import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.psiUtil.getChildOfType -import org.jetbrains.kotlin.psi.psiUtil.isTopLevelKtOrJavaMember import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments import org.jetbrains.kotlin.utils.addToStdlib.safeAs @@ -175,6 +164,7 @@ class DefaultPsiToDocumentableTranslator( private suspend fun parseClasslike(psi: PsiClass, parent: DRI): DClasslike = coroutineScope { with(psi) { val dri = parent.withClass(name.toString()) + val sources = PsiDocumentableSource(this).toSourceSetDependent() val superMethodsKeys = hashSetOf() val superMethods = mutableListOf>() methods.asIterable().parallelForEach { superMethodsKeys.add(it.hash) } @@ -214,14 +204,18 @@ class DefaultPsiToDocumentableTranslator( return AncestryNode( typeConstructor = GenericTypeConstructor( - DRI.from(psiClass), - psiClass.typeParameters.map { typeParameter -> + dri = DRI.from(psiClass), + projections = psiClass.typeParameters.map { typeParameter -> TypeParameter( dri = DRI.from(typeParameter), name = typeParameter.name.orEmpty(), - extra = typeParameter.extras() + sources = sources, + extra = PropertyContainer.withAll( + typeParameter.annotations(), + ) ) - } + }, + sources = sources ), superclass = classes.singleOrNull()?.first?.let(::traversePsiClassForAncestorsAndInheritedMembers), interfaces = interfaces.map { traversePsiClassForAncestorsAndInheritedMembers(it.first) } @@ -240,7 +234,6 @@ class DefaultPsiToDocumentableTranslator( ) else null } + superMethods.filter { it.first !in overridden }.parallelMap { parseFunction(it.first, inheritedFrom = it.second) } } - val source = PsiDocumentableSource(this).toSourceSetDependent() val classlikes = async { innerClasses.asIterable().parallelMap { parseClasslike(it, dri) } } val visibility = getVisibility().toSourceSetDependent() val ancestors = (listOfNotNull(ancestry.superclass?.let { @@ -257,112 +250,108 @@ class DefaultPsiToDocumentableTranslator( when { isAnnotationType -> DAnnotation( - name.orEmpty(), - dri, - documentation, - null, - source, - allFunctions.await(), - fields.mapNotNull { parseField(it, accessors[it].orEmpty()) }, - classlikes.await(), - visibility, - null, - constructors.map { parseFunction(it, true) }, - mapTypeParameters(dri), - setOf(sourceSetData), - false, - PropertyContainer.withAll( + name = name.orEmpty(), + dri = dri, + documentation = documentation, + expectPresentInSet = null, + sources = sources, + functions = allFunctions.await(), + properties = fields.mapNotNull { parseField(it, accessors[it].orEmpty()) }, + classlikes = classlikes.await(), + visibility = visibility, + companion = null, + constructors = constructors.map { parseFunction(it, true) }, + generics = mapTypeParameters(dri, sources), + sourceSets = setOf(sourceSetData), + isExpectActual = false, + extra = PropertyContainer.withAll( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations(), - getSourceLanguage() + .toAnnotations() ) ) isEnum -> DEnum( - dri, - name.orEmpty(), - fields.filterIsInstance().map { entry -> + dri = dri, + name = name.orEmpty(), + entries = fields.filterIsInstance().map { entry -> DEnumEntry( - dri.withClass(entry.name).withEnumEntryExtra(), - entry.name, - javadocParser.parseDocumentation(entry).toSourceSetDependent(), - null, - emptyList(), - emptyList(), - emptyList(), - setOf(sourceSetData), - PropertyContainer.withAll( + dri = dri.withClass(entry.name).withEnumEntryExtra(), + name = entry.name, + documentation = javadocParser.parseDocumentation(entry).toSourceSetDependent(), + expectPresentInSet = null, + functions = emptyList(), + properties = emptyList(), + classlikes = emptyList(), + sourceSets = setOf(sourceSetData), + sources = sources, + extra = PropertyContainer.withAll( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations(), - psi.getSourceLanguage() + .toAnnotations() ) ) }, - documentation, - null, - source, - allFunctions.await(), - fields.filter { it !is PsiEnumConstant }.map { parseField(it, accessors[it].orEmpty()) }, - classlikes.await(), - visibility, - null, - constructors.map { parseFunction(it, true) }, - ancestors, - setOf(sourceSetData), - false, - PropertyContainer.withAll( + documentation = documentation, + expectPresentInSet = null, + sources = sources, + functions = allFunctions.await(), + properties = fields.filter { it !is PsiEnumConstant }.map { parseField(it, accessors[it].orEmpty()) }, + classlikes = classlikes.await(), + visibility = visibility, + companion = null, + constructors = constructors.map { parseFunction(it, true) }, + supertypes = ancestors, + sourceSets = setOf(sourceSetData), + isExpectActual = false, + extra = PropertyContainer.withAll( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations(), - psi.getSourceLanguage() + .toAnnotations() ) ) isInterface -> DInterface( - dri, - name.orEmpty(), - documentation, - null, - source, - allFunctions.await(), - fields.mapNotNull { parseField(it, accessors[it].orEmpty()) }, - classlikes.await(), - visibility, - null, - mapTypeParameters(dri), - ancestors, - setOf(sourceSetData), - false, - PropertyContainer.withAll( + dri = dri, + name = name.orEmpty(), + documentation = documentation, + expectPresentInSet = null, + sources = sources, + functions = allFunctions.await(), + properties = fields.mapNotNull { parseField(it, accessors[it].orEmpty()) }, + classlikes = classlikes.await(), + visibility = visibility, + companion = null, + generics = mapTypeParameters(dri, sources), + supertypes = ancestors, + sourceSets = setOf(sourceSetData), + isExpectActual = false, + extra = PropertyContainer.withAll( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations(), - psi.getSourceLanguage() + .toAnnotations() ) ) else -> DClass( - dri, - name.orEmpty(), - constructors.map { parseFunction(it, true) }, - allFunctions.await(), - fields.mapNotNull { parseField(it, accessors[it].orEmpty()) }, - classlikes.await(), - source, - visibility, - null, - mapTypeParameters(dri), - ancestors, - documentation, - null, - modifiers, - setOf(sourceSetData), - false, - PropertyContainer.withAll( + dri = dri, + name = name.orEmpty(), + constructors = constructors.map { parseFunction(it, true) }, + functions = allFunctions.await(), + properties = fields.mapNotNull { parseField(it, accessors[it].orEmpty()) }, + classlikes = classlikes.await(), + sources = sources, + visibility = visibility, + companion = null, + generics = mapTypeParameters(dri, sources), + supertypes = ancestors, + documentation = documentation, + expectPresentInSet = null, + modifier = modifiers, + sourceSets = setOf(sourceSetData), + isExpectActual = false, + extra = PropertyContainer.withAll( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() .toAnnotations(), - ancestry.exceptionInSupertypesOrNull(), - psi.getSourceLanguage() + ancestry.exceptionInSupertypesOrNull() ) ) } @@ -382,40 +371,41 @@ class DefaultPsiToDocumentableTranslator( DRI.from(psi).copy(packageName = dri.packageName, classNames = dri.classNames) } ?: DRI.from(psi) val docs = javadocParser.parseDocumentation(psi) + val sources = PsiDocumentableSource(psi).toSourceSetDependent() return DFunction( - dri, - psi.name, - isConstructor, - psi.parameterList.parameters.map { psiParameter -> + dri = dri, + name = psi.name, + isConstructor = isConstructor, + parameters = psi.parameterList.parameters.map { psiParameter -> DParameter( - dri.copy(target = dri.target.nextTarget()), - psiParameter.name, - DocumentationNode( + dri = dri.copy(target = dri.target.nextTarget()), + name = psiParameter.name, + documentation = DocumentationNode( listOfNotNull(docs.firstChildOfTypeOrNull { it.name == psiParameter.name }) ).toSourceSetDependent(), - null, - getBound(psiParameter.type), - setOf(sourceSetData), - PropertyContainer.withAll( + expectPresentInSet = null, + type = getBound(psiParameter.type, sources), + sources = sources, + sourceSets = setOf(sourceSetData), + extra = PropertyContainer.withAll( psiParameter.annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations(), - psi.getSourceLanguage() + .toAnnotations() ) ) }, - docs.toSourceSetDependent(), - null, - PsiDocumentableSource(psi).toSourceSetDependent(), - psi.getVisibility().toSourceSetDependent(), - psi.returnType?.let { getBound(type = it) } ?: Void, - psi.mapTypeParameters(dri), - null, - psi.getModifier().toSourceSetDependent(), - setOf(sourceSetData), - false, - psi.additionalExtras().let { + documentation = docs.toSourceSetDependent(), + expectPresentInSet = null, + sources = PsiDocumentableSource(psi).toSourceSetDependent(), + visibility = psi.getVisibility().toSourceSetDependent(), + type = psi.returnType?.let { getBound(type = it, sources) } ?: Void(sources), + generics = psi.mapTypeParameters(dri, sources), + receiver = null, + modifier = psi.getModifier().toSourceSetDependent(), + sourceSets = setOf(sourceSetData), + isExpectActual = false, + extra = psi.additionalExtras().let { PropertyContainer.withAll( InheritedMember(inheritedFrom.toSourceSetDependent()), it.toSourceSetDependent().toAdditionalModifiers(), @@ -424,19 +414,12 @@ class DefaultPsiToDocumentableTranslator( .toAnnotations(), ObviousMember.takeIf { inheritedFrom != null && inheritedFrom.isObvious }, psi.throwsList.toDriList().takeIf { it.isNotEmpty() } - ?.let { CheckedExceptions(it.toSourceSetDependent()) }, - psi.getSourceLanguage() + ?.let { CheckedExceptions(it.toSourceSetDependent()) } ) } ) } - private fun PsiElement.getSourceLanguage() = when (this.containingFile.fileType) { - is KotlinFileType -> SourceLanguage(Language.KOTLIN) - is JavaFileType, is JavaClassFileType -> SourceLanguage(Language.JAVA) - else -> SourceLanguage(Language.UNKNOWN) - } - private fun PsiReferenceList.toDriList() = referenceElements.mapNotNull { it?.resolve()?.let { DRI.from(it) } } private fun PsiModifierListOwner.additionalExtras() = listOfNotNull( @@ -456,53 +439,62 @@ class DefaultPsiToDocumentableTranslator( Annotations.Annotation(DRI("kotlin.jvm", "JvmStatic"), emptyMap()) } - private fun PsiTypeParameter.extras(): PropertyContainer where T : AnnotationTarget, T : HasSourceLanguage = - PropertyContainer.withAll( - this.annotations.toList().toListOfAnnotations().toSourceSetDependent().toAnnotations(), - this.containingFile.getSourceLanguage() - ) - private fun PsiType.extras(): PropertyContainer where T : AnnotationTarget, T : HasSourceLanguage = - PropertyContainer.withAll( - this.annotations.toList().toListOfAnnotations().toSourceSetDependent().toAnnotations(), - SourceLanguage(Language.JAVA) - ) + private fun PsiTypeParameter.annotations() = + this.annotations.toList().toListOfAnnotations().toSourceSetDependent().toAnnotations() + private fun PsiType.annotations() = + this.annotations.toList().toListOfAnnotations().toSourceSetDependent().toAnnotations() - private fun getBound(type: PsiType): Bound { - fun bound() = when (type) { + private fun getBound(type: PsiType, sources: SourceSetDependent): Bound { + fun bound(sources: SourceSetDependent): Bound = when (type) { is PsiClassReferenceType -> type.resolve()?.let { resolved -> when { - resolved.qualifiedName == "java.lang.Object" -> JavaObject(type.extras()) + resolved.qualifiedName == "java.lang.Object" -> JavaObject( + sources = sources, + extra = PropertyContainer.withAll(type.annotations()) + ) resolved is PsiTypeParameter -> { TypeParameter( dri = DRI.from(resolved), name = resolved.name.orEmpty(), - extra = type.extras() + sources = sources, + extra = PropertyContainer.withAll(type.annotations()) ) } Regex("kotlin\\.jvm\\.functions\\.Function.*").matches(resolved.qualifiedName ?: "") || Regex("java\\.util\\.function\\.Function.*").matches( resolved.qualifiedName ?: "" ) -> FunctionalTypeConstructor( - DRI.from(resolved), - type.parameters.map { getProjection(it) }, - extra = type.extras() + dri = DRI.from(resolved), + projections = type.parameters.map { getProjection(it, sources) }, + sources = sources, + extra = PropertyContainer.withAll(type.annotations()) ) else -> GenericTypeConstructor( - DRI.from(resolved), - type.parameters.map { getProjection(it) }, - extra = type.extras() + dri = DRI.from(resolved), + projections = type.parameters.map { getProjection(it, sources) }, + sources = sources, + extra = PropertyContainer.withAll(type.annotations()) ) } - } ?: UnresolvedBound(type.presentableText, type.extras()) + } ?: UnresolvedBound( + name = type.presentableText, + sources = sources, + extra = PropertyContainer.withAll(type.annotations()) + ) is PsiArrayType -> GenericTypeConstructor( - DRI("kotlin", "Array"), - listOf(getProjection(type.componentType)), - extra = type.extras() + dri = DRI("kotlin", "Array"), + projections = listOf(getProjection(type.componentType, sources)), + sources = sources, + extra = PropertyContainer.withAll(type.annotations()) + ) + is PsiPrimitiveType -> if (type.name == "void") Void(sources) + else PrimitiveJavaType( + name = type.name, + sources = sources, + extra = PropertyContainer.withAll(type.annotations()) ) - is PsiPrimitiveType -> if (type.name == "void") Void - else PrimitiveJavaType(type.name, type.extras()) - is PsiImmediateClassType -> JavaObject(type.extras()) + is PsiImmediateClassType -> JavaObject(sources, PropertyContainer.withAll(type.annotations())) else -> throw IllegalStateException("${type.presentableText} is not supported by PSI parser") } @@ -510,24 +502,24 @@ class DefaultPsiToDocumentableTranslator( //but if this is the case, we treat them as 'one of' return if (type.annotations.toList().toListOfAnnotations().isEmpty()) { cachedBounds.getOrPut(type.canonicalText) { - bound() + bound(sources) } } else { - bound() + bound(sources) } } - private fun getVariance(type: PsiWildcardType): Projection = when { - type.extendsBound != PsiType.NULL -> Covariance(getBound(type.extendsBound)) - type.superBound != PsiType.NULL -> Contravariance(getBound(type.superBound)) + private fun getVariance(type: PsiWildcardType, sources: SourceSetDependent) = when { + type.extendsBound != PsiType.NULL -> Covariance(getBound(type.extendsBound, sources)) + type.superBound != PsiType.NULL -> Contravariance(getBound(type.superBound, sources)) else -> throw IllegalStateException("${type.presentableText} has incorrect bounds") } - private fun getProjection(type: PsiType): Projection = when (type) { + private fun getProjection(type: PsiType, sources: SourceSetDependent): Projection = when (type) { is PsiEllipsisType -> Star - is PsiWildcardType -> getVariance(type) - else -> getBound(type) + is PsiWildcardType -> getVariance(type, sources) + else -> getBound(type, sources) } private fun PsiModifierListOwner.getModifier() = when { @@ -536,24 +528,27 @@ class DefaultPsiToDocumentableTranslator( else -> JavaModifier.Empty } - private fun PsiTypeParameterListOwner.mapTypeParameters(dri: DRI): List { + private fun PsiTypeParameterListOwner.mapTypeParameters( + dri: DRI, + sources: SourceSetDependent + ): List { fun mapBounds(bounds: Array): List = if (bounds.isEmpty()) emptyList() else bounds.mapNotNull { - (it as? PsiClassType)?.let { classType -> Nullable(getBound(classType)) } + (it as? PsiClassType)?.let { classType -> Nullable(getBound(classType, sources)) } } return typeParameters.map { type -> DTypeParameter( - dri.copy(target = dri.target.nextTarget()), - type.name.orEmpty(), - null, - javadocParser.parseDocumentation(type).toSourceSetDependent(), - null, - mapBounds(type.bounds), - setOf(sourceSetData), - PropertyContainer.withAll( + dri = dri.copy(target = dri.target.nextTarget()), + name = type.name.orEmpty(), + presentableName = null, + documentation = javadocParser.parseDocumentation(type).toSourceSetDependent(), + expectPresentInSet = null, + bounds = mapBounds(type.bounds), + sources = sources, + sourceSets = setOf(sourceSetData), + extra = PropertyContainer.withAll( type.annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations(), - getSourceLanguage() + .toAnnotations() ) ) } @@ -585,28 +580,28 @@ class DefaultPsiToDocumentableTranslator( private fun parseField(psi: PsiField, accessors: List): DProperty { val dri = DRI.from(psi) + val sources = PsiDocumentableSource(psi).toSourceSetDependent() return DProperty( - dri, - psi.name, - javadocParser.parseDocumentation(psi).toSourceSetDependent(), - null, - PsiDocumentableSource(psi).toSourceSetDependent(), - psi.getVisibility().toSourceSetDependent(), - getBound(psi.type), - null, - accessors.firstOrNull { it.hasParameters() }?.let { parseFunction(it) }, - accessors.firstOrNull { it.returnType == psi.type }?.let { parseFunction(it) }, - psi.getModifier().toSourceSetDependent(), - setOf(sourceSetData), - emptyList(), - false, - psi.additionalExtras().let { + dri = dri, + name = psi.name, + documentation = javadocParser.parseDocumentation(psi).toSourceSetDependent(), + expectPresentInSet = null, + sources = sources, + visibility = psi.getVisibility().toSourceSetDependent(), + type = getBound(psi.type, sources), + receiver = null, + setter = accessors.firstOrNull { it.hasParameters() }?.let { parseFunction(it) }, + getter = accessors.firstOrNull { it.returnType == psi.type }?.let { parseFunction(it) }, + modifier = psi.getModifier().toSourceSetDependent(), + sourceSets = setOf(sourceSetData), + generics = emptyList(), + isExpectActual = false, + extra = psi.additionalExtras().let { PropertyContainer.withAll( it.toSourceSetDependent().toAdditionalModifiers(), (psi.annotations.toList() .toListOfAnnotations() + it.toListOfAnnotations()).toSourceSetDependent() - .toAnnotations(), - psi.getSourceLanguage() + .toAnnotations() ) } ) diff --git a/plugins/base/src/test/kotlin/filter/KotlinArrayDocumentableReplacerTest.kt b/plugins/base/src/test/kotlin/filter/KotlinArrayDocumentableReplacerTest.kt index b9b1dc1eb5..464b28f79a 100644 --- a/plugins/base/src/test/kotlin/filter/KotlinArrayDocumentableReplacerTest.kt +++ b/plugins/base/src/test/kotlin/filter/KotlinArrayDocumentableReplacerTest.kt @@ -6,6 +6,8 @@ import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test +import kotlin.test.assertTrue +import kotlin.test.asserter class KotlinArrayDocumentableReplacerTest : BaseAbstractTest() { private val configuration = dokkaConfiguration { @@ -41,7 +43,7 @@ class KotlinArrayDocumentableReplacerTest : BaseAbstractTest() { Assertions.assertEquals(typeArrayNames.size, params?.size) params?.forEachIndexed{ i, param -> - Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin", typeArrayNames[i]), emptyList()), + assertEqualsWithoutSources(GenericTypeConstructor(DRI("kotlin", typeArrayNames[i]), emptyList()), param.type) } } @@ -63,13 +65,13 @@ class KotlinArrayDocumentableReplacerTest : BaseAbstractTest() { ) { preMergeDocumentablesTransformationStage = { val params = it.firstOrNull()?.packages?.firstOrNull()?.functions?.firstOrNull()?.parameters - Assertions.assertEquals( + assertEqualsWithoutSources( Invariance(GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList())), (params?.firstOrNull()?.type as? GenericTypeConstructor)?.projections?.firstOrNull()) - Assertions.assertEquals( + assertEqualsWithoutSources( Invariance(GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList())), (params?.get(1)?.type as? FunctionalTypeConstructor)?.projections?.get(0)) - Assertions.assertEquals( + assertEqualsWithoutSources( Invariance(GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList())), (params?.get(1)?.type as? FunctionalTypeConstructor)?.projections?.get(1)) } @@ -98,11 +100,11 @@ class KotlinArrayDocumentableReplacerTest : BaseAbstractTest() { val myTestClass = it.firstOrNull()?.packages?.firstOrNull()?.classlikes?.firstOrNull() val property = myTestClass?.properties?.firstOrNull() - Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()), + assertEqualsWithoutSources(GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()), property?.type) - Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()), + assertEqualsWithoutSources(GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()), property?.getter?.type) - Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()), + assertEqualsWithoutSources(GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()), property?.setter?.parameters?.firstOrNull()?.type) } } @@ -123,7 +125,7 @@ class KotlinArrayDocumentableReplacerTest : BaseAbstractTest() { preMergeDocumentablesTransformationStage = { val arrTypealias = it.firstOrNull()?.packages?.firstOrNull()?.typealiases?.firstOrNull() - Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList()), + assertEqualsWithoutSources(GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList()), arrTypealias?.underlyingType?.firstOrNull()?.value) } } @@ -146,9 +148,9 @@ class KotlinArrayDocumentableReplacerTest : BaseAbstractTest() { val testFun = it.firstOrNull()?.packages?.firstOrNull()?.functions?.firstOrNull() val myTestClass = it.firstOrNull()?.packages?.firstOrNull()?.classlikes?.firstOrNull() as? DClass - Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin","IntArray"), emptyList()), + assertEqualsWithoutSources(GenericTypeConstructor(DRI("kotlin","IntArray"), emptyList()), testFun?.generics?.firstOrNull()?.bounds?.firstOrNull()) - Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin","IntArray"), emptyList()), + assertEqualsWithoutSources(GenericTypeConstructor(DRI("kotlin","IntArray"), emptyList()), myTestClass?.generics?.firstOrNull()?.bounds?.firstOrNull()) } } @@ -185,15 +187,41 @@ class KotlinArrayDocumentableReplacerTest : BaseAbstractTest() { ) { preMergeDocumentablesTransformationStage = { val paramsJS = it[1].packages.firstOrNull()?.functions?.firstOrNull()?.parameters - Assertions.assertNotEquals( + assertNotEqualsWithoutSources( GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList()), paramsJS?.firstOrNull()?.type) val paramsJVM = it.firstOrNull()?.packages?.firstOrNull()?.functions?.firstOrNull()?.parameters - Assertions.assertEquals( + assertEqualsWithoutSources( GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList()), paramsJVM?.firstOrNull()?.type) } } } + /** Fake extension constructor to simplify code */ + private fun GenericTypeConstructor(dri: DRI, projections: List): GenericTypeConstructor = + GenericTypeConstructor(dri, projections, sources = emptyMap()) + /** + * Testing equality for DocumentableSources is difficult and rarely useful. However, the change that added this + * involved changes in KotlinArrayDocumentableReplacer's treatment of sources, so some tests are present. + */ + private fun assertEqualsWithoutSources(bound: Bound, other: Bound?) = + Assertions.assertTrue(other!!.equalsWithoutSources(bound)) + .also { assertContains(other.sources.values.single().path, "Test", "kt") } + private fun assertEqualsWithoutSources(vari: Variance<*>, other: Projection?) = + Assertions.assertEquals(vari::class, other!!::class).also { + Assertions.assertTrue((other as Variance<*>).inner.equalsWithoutSources(vari.inner)) } + .also { assertContains((other as WithSources).sources.values.single().path, "Test", "kt") } + private fun assertNotEqualsWithoutSources(bound: Bound, other: Bound?) = + Assertions.assertFalse(other!!.equalsWithoutSources(bound)) + .also { assertContains(other.sources.values.single().path, "Test", "kt") } + private fun Bound.equalsWithoutSources(other: Bound): Boolean { + if (this !is GenericTypeConstructor || other !is GenericTypeConstructor) return false + return this.dri == other.dri && this.projections == other.projections + } + /** Source duplicated from kotlin.test as that kotlin.test.assertContains would not resolve. */ + private fun assertContains(charSequence: String, vararg other: String) = asserter.assertTrue( + { "Expected the string to contain the substring(s).\nString <$charSequence>, substring(s) <$other>." }, + other.all { charSequence.contains(it, ignoreCase = false) } + ) } \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/linking/EnumValuesLinkingTest.kt b/plugins/base/src/test/kotlin/linking/EnumValuesLinkingTest.kt index c5c0f33bec..926e27d4f3 100644 --- a/plugins/base/src/test/kotlin/linking/EnumValuesLinkingTest.kt +++ b/plugins/base/src/test/kotlin/linking/EnumValuesLinkingTest.kt @@ -38,7 +38,7 @@ class EnumValuesLinkingTest : BaseAbstractTest() { assertEquals(4, classlikes.size) val javaLinker = classlikes.single { it.name == "JavaLinker" } as DClass - assertEquals(javaLinker.extra[SourceLanguage]!!.sourceLanguage, Language.JAVA) + assertEquals(javaLinker.sourceLanguage, Language.JAVA) javaLinker.documentation.values.single().children.run { when (val kotlinLink = this[0].children[1].children[1]) { is DocumentationLink -> kotlinLink.dri.run { @@ -60,7 +60,7 @@ class EnumValuesLinkingTest : BaseAbstractTest() { } val kotlinLinker = classlikes.single { it.name == "KotlinLinker" } as DClass - assertEquals(kotlinLinker.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) + assertEquals(kotlinLinker.sourceLanguage, Language.KOTLIN) kotlinLinker.documentation.values.single().children.run { when (val kotlinLink = this[0].children[0].children[5]) { is DocumentationLink -> kotlinLink.dri.run { @@ -82,9 +82,9 @@ class EnumValuesLinkingTest : BaseAbstractTest() { } val kotlinEnum = classlikes.single { it.name == "KotlinEnum" } as DEnum - assertEquals(kotlinEnum.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) + assertEquals(kotlinEnum.sourceLanguage, Language.KOTLIN) val javaEnum = classlikes.single { it.name == "JavaEnum" } as DEnum - assertEquals(javaEnum.extra[SourceLanguage]!!.sourceLanguage, Language.JAVA) + assertEquals(javaEnum.sourceLanguage, Language.JAVA) assertEquals( javaLinker.documentation.values.single().children[0].children[1].children[1].safeAs()?.dri, diff --git a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt index 2b3e41fb3f..7eb5c9f617 100644 --- a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt +++ b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt @@ -695,7 +695,7 @@ class DefaultDescriptorToDocumentableTranslatorTest : BaseAbstractTest() { Annotations.Annotation(DRI("sample", "Hello"), emptyMap()), type.extra[Annotations]?.directAnnotations?.values?.single()?.single() ) - assertEquals(type.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) + assertEquals(type.sourceLanguage, Language.KOTLIN) } } } @@ -724,7 +724,7 @@ class DefaultDescriptorToDocumentableTranslatorTest : BaseAbstractTest() { type.extra[Annotations]?.directAnnotations?.values?.single()?.single() ) assertEquals("kotlin/Int///PointingToDeclaration/", type.dri.toString()) - assertEquals(type.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) + assertEquals(type.sourceLanguage, Language.KOTLIN) } } } diff --git a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt index 0b84412015..c36664cf89 100644 --- a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt +++ b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt @@ -258,13 +258,13 @@ class DefaultPsiToDocumentableTranslatorTest : BaseAbstractTest() { "String", (kotlinSubclassFunction.parameters.firstOrNull()?.type as? TypeConstructor)?.dri?.classNames ) - assertEquals(javaLeafClass.extra[SourceLanguage]!!.sourceLanguage, Language.JAVA) - assertEquals(kotlinSubClass.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) - assertEquals(kotlinSubclassFunction.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) + assertEquals(javaLeafClass.sourceLanguage, Language.JAVA) + assertEquals(kotlinSubClass.sourceLanguage, Language.KOTLIN) + assertEquals(kotlinSubclassFunction.sourceLanguage, Language.KOTLIN) // This function was defined in Kotlin and inherited by a Java class without overriding. // Its param types, if no nullability information is present, default to non-null instead of platform. - assertEquals(kotlinLeafClassFunction.extra[SourceLanguage]!!.sourceLanguage, Language.KOTLIN) - assertEquals(javaLeafClassFunction.extra[SourceLanguage]!!.sourceLanguage, Language.JAVA) + assertEquals(kotlinLeafClassFunction.sourceLanguage, Language.KOTLIN) + assertEquals(javaLeafClassFunction.sourceLanguage, Language.JAVA) } } } diff --git a/plugins/base/src/test/kotlin/translators/ExternalDocumentablesTest.kt b/plugins/base/src/test/kotlin/translators/ExternalDocumentablesTest.kt index 011ae72974..579d9406eb 100644 --- a/plugins/base/src/test/kotlin/translators/ExternalDocumentablesTest.kt +++ b/plugins/base/src/test/kotlin/translators/ExternalDocumentablesTest.kt @@ -7,6 +7,7 @@ import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.base.translators.descriptors.ExternalDocumentablesProvider import org.jetbrains.dokka.model.DClass import org.jetbrains.dokka.model.DInterface +import org.jetbrains.dokka.model.Language import org.jetbrains.dokka.plugability.plugin import org.jetbrains.dokka.plugability.querySingle import org.jetbrains.dokka.utilities.cast @@ -31,6 +32,8 @@ class ExternalDocumentablesTest : BaseAbstractTest() { /src/com/sample/MyList.kt package com.sample class MyList: ArrayList() + + class MyJsonThingy: """.trimIndent(), configuration ) { @@ -52,6 +55,8 @@ class ExternalDocumentablesTest : BaseAbstractTest() { listOf("AbstractList", "RandomAccess", "Cloneable", "Serializable", "MutableList"), supertypes ) + assertEquals(Language.JAVA, res!!.sources.values.single().language) + assertEquals("JavaStandardLibrary.java", res.sources.values.single().path) } } } @@ -98,6 +103,8 @@ class ExternalDocumentablesTest : BaseAbstractTest() { listOf("CoroutineContext.Element"), supertypes ) + assertEquals(Language.KOTLIN, res!!.sources.values.single().language) + assertEquals("KotlinBuiltins.kt", res.sources.values.single().path) } } } @@ -133,7 +140,9 @@ class ExternalDocumentablesTest : BaseAbstractTest() { entry.key) assertEquals("Entry", res?.name) assertEquals("kotlin.collections/Map.Entry///PointingToDeclaration/", res?.dri?.toString()) + assertEquals(Language.KOTLIN, res!!.sources.values.single().language) + assertEquals("KotlinBuiltins.kt", res.sources.values.single().path) } } } -} \ No newline at end of file +} diff --git a/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt index 1e3bd80042..d156e6b39b 100644 --- a/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt +++ b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt @@ -141,8 +141,7 @@ internal fun DProperty.javaAccessors(isTopLevel: Boolean = false, relocateToClas sourceSets.associateWith { setOf(ExtraModifiers.JavaOnlyModifiers.Static) } - ) - else getter.extra + ) else getter.extra ) }, setter?.let { setter -> @@ -165,13 +164,12 @@ internal fun DProperty.javaAccessors(isTopLevel: Boolean = false, relocateToClas }, modifier = javaModifierFromSetter(), visibility = visibility.mapValues { JavaVisibility.Public }, - type = Void, + type = Void(sources), extra = if (isTopLevel) setter.extra + setter.extra.mergeAdditionalModifiers( sourceSets.associateWith { setOf(ExtraModifiers.JavaOnlyModifiers.Static) } - ) - else setter.extra + ) else setter.extra ) } ) @@ -278,7 +276,7 @@ internal fun DClass.functionsInJava(): List = .flatMap { it.asJava(dri.classNames ?: name) } private fun DTypeParameter.asJava(): DTypeParameter = copy( - variantTypeParameter = variantTypeParameter.withDri(dri.possiblyAsJava()), + variantTypeParameter = variantTypeParameter.withDri(dri.possiblyAsJava(), sources), bounds = bounds.map { it.asJava() } ) @@ -353,7 +351,7 @@ internal fun DObject.asJava(): DObject = copy( visibility = sourceSets.associateWith { JavaVisibility.Public }, - type = GenericTypeConstructor(dri, emptyList()), + type = GenericTypeConstructor(dri = dri, projections = emptyList(), sources = sources), setter = null, getter = null, sourceSets = sourceSets, From 18a2cd533d0ad1df750dab75f889c1adcc482316 Mon Sep 17 00:00:00 2001 From: Owen Gray Date: Tue, 7 Jun 2022 09:13:04 -0400 Subject: [PATCH 3/3] More work on determining source file of descriptors with no ContainingFile attribute This is not complete. --- .../kotlin/model/documentableProperties.kt | 2 +- .../jetbrains/dokka/analysis/Documentable.kt | 114 ++++++++-- .../DocumentableReplacerTransformer.kt | 2 +- .../documentables/DefaultPageCreator.kt | 1 + .../translators/ExternalDocumentablesTest.kt | 51 +++-- .../test/kotlin/translators/SourcesTest.kt | 210 ++++++++++++++++++ 6 files changed, 346 insertions(+), 34 deletions(-) create mode 100644 plugins/base/src/test/kotlin/translators/SourcesTest.kt diff --git a/core/src/main/kotlin/model/documentableProperties.kt b/core/src/main/kotlin/model/documentableProperties.kt index 39350c6097..d77381d03d 100644 --- a/core/src/main/kotlin/model/documentableProperties.kt +++ b/core/src/main/kotlin/model/documentableProperties.kt @@ -48,6 +48,6 @@ data class CheckedExceptions(val exceptions: SourceSetDependent>) : Ex } enum class Language { - JAVA, KOTLIN, UNKNOWN + JAVA, KOTLIN, XML, JS, UNKNOWN } diff --git a/kotlin-analysis/src/main/kotlin/org/jetbrains/dokka/analysis/Documentable.kt b/kotlin-analysis/src/main/kotlin/org/jetbrains/dokka/analysis/Documentable.kt index fcf5ba8c0b..bc3d7b95be 100644 --- a/kotlin-analysis/src/main/kotlin/org/jetbrains/dokka/analysis/Documentable.kt +++ b/kotlin-analysis/src/main/kotlin/org/jetbrains/dokka/analysis/Documentable.kt @@ -6,46 +6,82 @@ import com.intellij.psi.PsiFile import com.intellij.psi.PsiNamedElement import org.jetbrains.dokka.model.DocumentableSource import org.jetbrains.dokka.model.Language +import org.jetbrains.kotlin.backend.jvm.FacadeClassSourceShimForFragmentCompilation import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.SourceFile.NO_SOURCE_FILE +import org.jetbrains.kotlin.descriptors.runtime.components.ReflectAnnotationSource import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi -import org.jetbrains.kotlin.load.kotlin.toSourceElement +import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageFragment +import org.jetbrains.kotlin.load.java.sources.JavaSourceElement +import org.jetbrains.kotlin.load.kotlin.* import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor +import org.jetbrains.kotlin.resolve.source.KotlinSourceElement +import org.jetbrains.kotlin.resolve.source.PsiSourceElement import org.jetbrains.kotlin.resolve.source.PsiSourceFile +import org.jetbrains.kotlin.descriptors.SourceElement.NO_SOURCE +import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor +import org.jetbrains.kotlin.load.java.structure.JavaClass + +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor +import org.jetbrains.kotlin.serialization.js.KotlinJavascriptPackageFragment +/** + * Logic tree for figuring out the source file of a Descriptor: + * 1. If the descriptor (or psi) has a non-null `containingFile` attribute, use that. + * 2. If the descriptor is or is in a synthetic method or property that overrides a real method or property + * (and thus the real method defines the API surface), use the overridden member's "real" source file + * 3. If the descriptor is contained within a class, recur onto the outermost class. + * (NOTE: this assumes that extension inner classes are not possible, which is a feature request) + * 4. If the descriptor or something in its containingDeclaration hierarchy is deserialized, return a .class file or a + * .jar file, which may be a LiteralSourceFile + * 5. Else create a LiteralSourceFile based on the package name of the class, with no full path. + */ data class DescriptorDocumentableSource(val descriptor: DeclarationDescriptor) : DocumentableSource { val file = descriptor.file() val psi = descriptor.findPsi() - override val path = file.name!! - override val language = file.getSourceLanguage() + override val path = file.toString() + override val language = + if (descriptor.containingHierarchyHasJavaClass() == (file.getSourceLanguage() == Language.JAVA)) + file.getSourceLanguage() + else throw RuntimeException("Two methods of finding source language do not agree: " + + "KSP is Java: ${descriptor.containingHierarchyHasJavaClass()}, " + + "while complex version is ${file.getSourceLanguage()}.") override fun toString() = "DescriptorDocumentableSource(path = $path, element = $psi)" } /** The source file in which this declaration exists. Declarations can be contained inside others in the same file. */ fun DeclarationDescriptor.file(): SourceFile = when { - this.source.name != null -> this.toSourceElement.containingFile + // Most PSIs/Descriptors will use this case. This case only fails with enum entries, params/types, and a few more. + this.source.name != null -> this.toSourceElement.containingFile // source is not NO_SOURCE // FAKE_OVERRIDE is used in autogenerated functions, like some equals methods. The upstream signature is retained. (this is DeserializedSimpleFunctionDescriptor && this.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) -> + this.overriddenDescriptors.first().file() // TODO: is first() appropriate here? + (this is PropertyDescriptor && this.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) -> this.overriddenDescriptors.first().file() - this is BuiltInsPackageFragment -> when { - this.fqName.asString().startsWith("kotlin") -> KOTLIN_BUILTINS // Inherited elements, e.g. enum.ordinal - this.fqName.asString().startsWith("java") -> JAVA_STDLIB - else -> throw RuntimeException("Unknown BuiltInsPackageFragment fqName: ${this.fqName.asString()}") - } - this is PackageFragmentDescriptor -> when { - this.fqName.asString().startsWith("kotlin") -> KOTLIN_BUILTINS // Mostly happens via external resolver - this.fqName.asString().startsWith("java") -> JAVA_STDLIB - else -> throw RuntimeException("Unknown BuiltInsPackageFragment fqName: ${this.fqName.asString()}") - } + //this is PackageFragmentDescriptor -> LiteralSourceFile(fqName.asString(), Language.JAVA) + this.toSourceElement.getSourceLanguage() != Language.UNKNOWN -> + LiteralSourceFile(getPackageName()!!, toSourceElement.getSourceLanguage()) this.containingDeclaration != null -> this.containingDeclaration!!.file() - else -> SourceElement.NO_SOURCE.containingFile + else -> NO_SOURCE.containingFile } -private val KOTLIN_BUILTINS = SourceFile { "KotlinBuiltins.kt" } -private val JAVA_STDLIB = SourceFile { "JavaStandardLibrary.java" } +private fun DeclarationDescriptor?.containingHierarchyHasJavaClass(): Boolean = when(this) { + null -> false + is JavaClassDescriptor -> true + else -> this.containingDeclaration.containingHierarchyHasJavaClass() +} + +private fun DeclarationDescriptor.getPackageName() = toSourceElement.getPackageName(this) ?: + containingDeclaration!!.toSourceElement.getPackageName(containingDeclaration!!) + +private class LiteralSourceFile(val _name: String, val language: Language, val path: String? = null) : SourceFile { + override fun getName() = _name + override fun toString() = this._name +} private val DeclarationDescriptor.source: SourceFile get() = this.toSourceElement.containingFile @@ -60,11 +96,51 @@ fun PsiFile.getSourceLanguage() = when (this.fileType) { else -> Language.UNKNOWN } +/** + * Returns the package of this (presumed) + * This function assumes that containingFile has already been tried and is NO_SOURCE + */ +private fun SourceElement.getPackageName(parent: DeclarationDescriptor): String? = + when { + parent is PackageFragmentDescriptor -> parent.fqName.asString() + //parent.containingDeclaration.toSourceElement.javaElement is BinaryJavaClass -> + else -> when(this) { + is KotlinJvmBinarySourceElement -> this.binaryClass.classId.asString() + is KotlinJvmBinaryPackageSourceElement -> + getContainingBinaryClass(parent as DeserializedMemberDescriptor)!!.classId.asString() + is KotlinSourceElement -> this.psi.name!! + is JavaSourceElement -> when (this.javaElement) { + is JavaClass -> (this.javaElement as JavaClass).fqName?.asString() + else -> this.javaElement.javaClass.canonicalName + } + is PsiSourceElement -> this.psi!!.text + is KotlinJavascriptPackageFragment.JsContainerSource -> this.javaClass.canonicalName + NO_SOURCE -> null + else -> throw RuntimeException("Could not get SourceElement PackageName for $this") + } +} + +private fun SourceElement.getSourceLanguage() = when (this) { + is KotlinSourceElement -> Language.KOTLIN + is KotlinJvmBinarySourceElement, is KotlinJvmBinaryPackageSourceElement -> Language.KOTLIN + // TODO: verify that RuntimeSourceElement : JavaSourceElement is actually always from Java source + is JavaSourceElement -> Language.JAVA + is KotlinJavascriptPackageFragment.JsContainerSource -> Language.JS + is PsiSourceElement -> containingFile.getSourceLanguage() + NO_SOURCE -> Language.UNKNOWN + is JvmPackagePartSource -> throw RuntimeException("Not yet handled case: JvmPackagePartSource") + is FacadeClassSourceShimForFragmentCompilation, is ReflectAnnotationSource -> + throw RuntimeException("Should not be able to get element of this type from real code: ${this::class.java}") + // This `when` is exhaustive over all SourceElement implementers in the compiler as of 1.7.0-RC + else -> + if((this::class.java).name == "XmlSourceElement") Language.XML // This class is private in the compiler + else throw RuntimeException("Unknown SourceElement type: ${this::class.java}") +} + fun SourceFile.getSourceLanguage() = when (this) { NO_SOURCE_FILE -> throw RuntimeException("No source file present") // For testing - KOTLIN_BUILTINS -> Language.KOTLIN - JAVA_STDLIB -> Language.JAVA + is LiteralSourceFile -> this.language //is JavaFileType, is JavaClassFileType -> Language.JAVA is PsiSourceFile -> { when (this.psiFile) { diff --git a/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt index 11a12d54d6..f5ef8ed152 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt @@ -219,7 +219,7 @@ abstract class DocumentableReplacerTransformer(val context: DokkaContext) : val wasChanged = underlyingType.any { it.value.changed } || generics.any { it.changed } return (dTypeAlias.takeIf { !wasChanged } ?: dTypeAlias.copy( underlyingType = underlyingType.mapValues { it.value.target ?: dTypeAlias.underlyingType.getValue(it.key) }, - generics = generics.mapNotNull { it.target }, + generics = generics.mapNotNull { it.target } )).let { AnyWithChanges(it, wasChanged) } } diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index 776969f9fd..83ec352dc7 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -706,6 +706,7 @@ open class DefaultPageCreator( when (it) { Language.KOTLIN -> firstParagraphComment(tag.root) Language.JAVA -> firstSentenceComment(tag.root) + Language.XML, Language.JS, Language.UNKNOWN -> firstParagraphComment(tag.root) } } ?: firstParagraphComment(tag.root) } diff --git a/plugins/base/src/test/kotlin/translators/ExternalDocumentablesTest.kt b/plugins/base/src/test/kotlin/translators/ExternalDocumentablesTest.kt index 579d9406eb..4a82288827 100644 --- a/plugins/base/src/test/kotlin/translators/ExternalDocumentablesTest.kt +++ b/plugins/base/src/test/kotlin/translators/ExternalDocumentablesTest.kt @@ -11,6 +11,7 @@ import org.jetbrains.dokka.model.Language import org.jetbrains.dokka.plugability.plugin import org.jetbrains.dokka.plugability.querySingle import org.jetbrains.dokka.utilities.cast +import org.jsoup.nodes.Element import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -22,7 +23,10 @@ class ExternalDocumentablesTest : BaseAbstractTest() { sourceSet { sourceRoots = listOf("src") analysisPlatform = "jvm" - classpath += jvmStdlibPath!! + classpath += listOf(jvmStdlibPath!!, + PathManager.getResourceRoot(Element::class.java, "/org/jsoup/nodes/Element.class")!! + .replaceAfter(".jar", "")) + } } } @@ -30,10 +34,11 @@ class ExternalDocumentablesTest : BaseAbstractTest() { testInline( """ /src/com/sample/MyList.kt + import org.jsoup.nodes.Element package com.sample class MyList: ArrayList() - class MyJsonThingy: + class Foo: org.jsoup.nodes.Element("foo") """.trimIndent(), configuration ) { @@ -41,22 +46,42 @@ class ExternalDocumentablesTest : BaseAbstractTest() { pluginsSetupStage = { provider = it.plugin().querySingle { externalDocumentablesProvider } } + val jfoo = org.jsoup.nodes.Element("jfoo") + println(jfoo::class.java) documentablesTransformationStage = { mod -> - val entry = mod.packages.single().classlikes.single().cast().supertypes.entries.single() - val res = provider.findClasslike( - entry.value.single().typeConstructor.dri, - entry.key) - assertEquals("ArrayList", res?.name) - assertEquals("java.util/ArrayList///PointingToDeclaration/", res?.dri?.toString()) + val listEntry = mod.packages.single().classlikes.single { it.name == "MyList" } + .cast().supertypes.entries.single() + val listRes = provider.findClasslike( + listEntry.value.single().typeConstructor.dri, + listEntry.key)!! + assertEquals("ArrayList", listRes.name) + assertEquals("java.util/ArrayList///PointingToDeclaration/", listRes.dri.toString()) - val supertypes = res?.cast()?.supertypes?.values?.single() - ?.map { it.typeConstructor.dri.classNames } + val listSupertypes = listRes.cast().supertypes.values.single() + .map { it.typeConstructor.dri.classNames } assertEquals( listOf("AbstractList", "RandomAccess", "Cloneable", "Serializable", "MutableList"), - supertypes + listSupertypes + ) + assertEquals(Language.JAVA, listRes.sources.values.single().language) + assertEquals("java.util", listRes.sources.values.single().path) + + val jsoupEntry = mod.packages.single().classlikes.single { it.name == "Foo" } + .cast().supertypes.entries.single() + val jsoupRes = provider.findClasslike( + jsoupEntry.value.single().typeConstructor.dri, + jsoupEntry.key)!! + assertEquals("Element", jsoupRes.name) + assertEquals("org.jsoup.nodes/Element///PointingToDeclaration/", jsoupRes.dri.toString()) + + val jsoupSupertypes = jsoupRes.cast().supertypes.values.single() + .map { it.typeConstructor.dri.classNames } + assertEquals( + listOf("Node"), + jsoupSupertypes ) - assertEquals(Language.JAVA, res!!.sources.values.single().language) - assertEquals("JavaStandardLibrary.java", res.sources.values.single().path) + assertEquals(Language.JAVA, jsoupRes.sources.values.single().language) + assertEquals("org.jsoup.nodes", jsoupRes.sources.values.single().path) } } } diff --git a/plugins/base/src/test/kotlin/translators/SourcesTest.kt b/plugins/base/src/test/kotlin/translators/SourcesTest.kt new file mode 100644 index 0000000000..88ddac8e32 --- /dev/null +++ b/plugins/base/src/test/kotlin/translators/SourcesTest.kt @@ -0,0 +1,210 @@ +package translators + +import com.intellij.openapi.application.PathManager +import kotlinx.coroutines.Job +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.translators.descriptors.ExternalDocumentablesProvider +import org.jetbrains.dokka.model.DClass +import org.jetbrains.dokka.model.DInterface +import org.jetbrains.dokka.model.Language +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.dokka.utilities.cast +import org.jsoup.nodes.Element +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class SourcesTest : BaseAbstractTest() { + @Test + fun `external documentable from java stdlib`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src") + analysisPlatform = "jvm" + classpath += listOf(jvmStdlibPath!!, + PathManager.getResourceRoot(Element::class.java, "/org/jsoup/nodes/Element.class")!! + .replaceAfter(".jar", ""), + PathManager.getResourceRoot(Language::class.java, "/com/fasterxml/jackson/module/kotlin/KotlinModule.class")!! + .replaceAfter(".jar", "") + ) + + } + } + } + + testInline( + """ + /src/com/sample/MyList.kt + import org.jsoup.nodes.Element + import com.fasterxml.jackson.module.kotlin.KotlinModule + package com.sample + class MyList: ArrayList() + + class Jextern: org.jsoup.nodes.Element("Jextern") + + class Kstd: IntArray() + + class Kextern: com.fasterxml.jackson.module.kotlin.KotlinModule + """.trimIndent(), + configuration + ) { + lateinit var provider: ExternalDocumentablesProvider + pluginsSetupStage = { + provider = it.plugin().querySingle { externalDocumentablesProvider } + } + + documentablesTransformationStage = { mod -> + val listEntry = mod.packages.single().classlikes.single { it.name == "MyList" } + .cast().supertypes.entries.single() + val listRes = provider.findClasslike( + listEntry.value.single().typeConstructor.dri, + listEntry.key)!! + assertEquals("ArrayList", listRes.name) + assertEquals("java.util/ArrayList///PointingToDeclaration/", listRes.dri.toString()) + + val listSupertypes = listRes.cast().supertypes.values.single() + .map { it.typeConstructor.dri.classNames } + assertEquals( + listOf("AbstractList", "RandomAccess", "Cloneable", "Serializable", "MutableList"), + listSupertypes + ) + assertEquals(Language.JAVA, listRes.sources.values.single().language) + assertEquals("java.util", listRes.sources.values.single().path) + + val jexternEntry = mod.packages.single().classlikes.single { it.name == "Jextern" } + .cast().supertypes.entries.single() + val jexternRes = provider.findClasslike( + jexternEntry.value.single().typeConstructor.dri, + jexternEntry.key)!! + assertEquals("Element", jexternRes.name) + assertEquals("org.jsoup.nodes/Element///PointingToDeclaration/", jexternRes.dri.toString()) + + val jexternSupertypes = jexternRes.cast().supertypes.values.single() + .map { it.typeConstructor.dri.classNames } + assertEquals( + listOf("Node"), + jexternSupertypes + ) + assertEquals(Language.JAVA, jexternRes.sources.values.single().language) + assertEquals("org.jsoup.nodes", jexternRes.sources.values.single().path) + + val kstdEntry = mod.packages.single().classlikes.single { it.name == "Kstd" } + .cast().supertypes.entries.single() + val kstdRes = provider.findClasslike( + kstdEntry.value.single().typeConstructor.dri, + kstdEntry.key)!! + assertEquals("IntArray", kstdRes.name) + assertEquals("kotlin/IntArray///PointingToDeclaration/", kstdRes.dri.toString()) + + val kstdSupertypes = kstdRes.cast().supertypes.values.single() + .map { it.typeConstructor.dri.classNames } + assertEquals( + listOf("Cloneable", "Serializable"), + kstdSupertypes + ) + assertEquals(Language.KOTLIN, kstdRes.sources.values.single().language) + assertEquals("KotlinBuiltins.kt", kstdRes.sources.values.single().path) + + val kexternEntry = mod.packages.single().classlikes.single { it.name == "Kextern" } + .cast().supertypes.entries.single() + val kexternRes = provider.findClasslike( + kexternEntry.value.single().typeConstructor.dri, + kexternEntry.key)!! + assertEquals("KotlinModule", kexternRes.name) + assertEquals("com.fasterxml.jackson.module.kotlin/KotlinModule///PointingToDeclaration/", kexternRes.dri.toString()) + + val kexternSupertypes = kexternRes.cast().supertypes.values.single() + .map { it.typeConstructor.dri.classNames } + assertEquals( + listOf("SimpleModule"), + kexternSupertypes + ) + assertEquals(Language.KOTLIN, kexternRes.sources.values.single().language) + assertEquals("com.fasterxml.jackson.module.kotlin", kexternRes.sources.values.single().path) + } + } + } + + @Test + fun `external documentable from dependency`() { + val coroutinesPath = + PathManager.getResourceRoot(Job::class.java, "/kotlinx/coroutines/Job.class") + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src") + analysisPlatform = "jvm" + classpath += listOf(jvmStdlibPath!!, coroutinesPath!!) + } + } + } + + testInline( + """ + /src/com/sample/MyJob.kt + package com.sample + import kotlinx.coroutines.Job + abstract class MyJob: Job + """.trimIndent(), + configuration + ) { + lateinit var provider: ExternalDocumentablesProvider + pluginsSetupStage = { + provider = it.plugin().querySingle { externalDocumentablesProvider } + } + documentablesTransformationStage = { mod -> + val entry = mod.packages.single().classlikes.single().cast().supertypes.entries.single() + val res = provider.findClasslike( + entry.value.single().typeConstructor.dri, + entry.key) + assertEquals("Job", res?.name) + assertEquals("kotlinx.coroutines/Job///PointingToDeclaration/", res?.dri?.toString()) + + val supertypes = res?.cast()?.supertypes?.values?.single() + ?.map { it.typeConstructor.dri.classNames } + assertEquals( + listOf("CoroutineContext.Element"), + supertypes + ) + } + } + } + + @Test + fun `external documentable for nested class`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src") + analysisPlatform = "jvm" + classpath += jvmStdlibPath!! + } + } + } + + testInline( + """ + /src/com/sample/MyList.kt + package com.sample + abstract class MyEntry: Map.Entry + """.trimIndent(), + configuration + ) { + lateinit var provider: ExternalDocumentablesProvider + pluginsSetupStage = { + provider = it.plugin().querySingle { externalDocumentablesProvider } + } + documentablesTransformationStage = { mod -> + val entry = mod.packages.single().classlikes.single().cast().supertypes.entries.single() + val res = provider.findClasslike( + entry.value.single().typeConstructor.dri, + entry.key) + assertEquals("Entry", res?.name) + assertEquals("kotlin.collections/Map.Entry///PointingToDeclaration/", res?.dri?.toString()) + } + } + } +} \ No newline at end of file