From 9a1434d583b931671e2c5e9c5275af725938d503 Mon Sep 17 00:00:00 2001 From: Andrzej Ratajczak <32793002+BarkingBad@users.noreply.github.com> Date: Thu, 17 Feb 2022 16:27:28 +0100 Subject: Refactor Ancestry Graphs (#2326) --- .../DefaultDescriptorToDocumentableTranslator.kt | 85 +++++-------- .../src/main/kotlin/translators/isException.kt | 9 ++ .../psi/DefaultPsiToDocumentableTranslator.kt | 134 ++++++++++----------- .../translators/psi/parsers/PsiCommentsUtils.kt | 4 +- 4 files changed, 106 insertions(+), 126 deletions(-) (limited to 'plugins/base/src') diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt index 6078b54d..96bd2fc0 100644 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt @@ -13,7 +13,7 @@ import org.jetbrains.dokka.analysis.KotlinAnalysis 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.isDirectlyAnException +import org.jetbrains.dokka.base.translators.typeConstructorsBeingExceptions import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser import org.jetbrains.dokka.base.translators.unquotedValue import org.jetbrains.dokka.links.* @@ -21,7 +21,6 @@ import org.jetbrains.dokka.links.Callable 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.* import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.plugability.DokkaContext @@ -215,8 +214,8 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent()), - info.exceptionsInSupertypes?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }, + ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), + info.ancestry.exceptionInSupertypesOrNull() ) ) } @@ -253,8 +252,8 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent()), - info.exceptionsInSupertypes?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }, + ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), + info.ancestry.exceptionInSupertypesOrNull() ) ) } @@ -298,7 +297,7 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent()) + ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()) ) ) } @@ -420,8 +419,8 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent()), - info.exceptionsInSupertypes?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }, + ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()), + info.ancestry.exceptionInSupertypesOrNull() ) ) } @@ -664,8 +663,10 @@ private class DokkaDescriptorVisitor( with(descriptor) { coroutineScope { val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } } - val info = buildAncestryInformation(listOf(underlyingType)).sortedBy { it.level } - + val info = buildAncestryInformation(defaultType).copy( + superclass = buildAncestryInformation(underlyingType), + interfaces = emptyList() + ) DTypeAlias( dri = DRI.from(this@with), name = name.asString(), @@ -678,8 +679,7 @@ private class DokkaDescriptorVisitor( generics = generics.await(), extra = PropertyContainer.withAll( descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - info.exceptionsInSupertypes()?.takeIf { it.isNotEmpty() } - ?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }, + info.exceptionInSupertypesOrNull(), ) ) } @@ -759,15 +759,10 @@ private class DokkaDescriptorVisitor( DRI.from(kt.constructor.declarationDescriptor as DeclarationDescriptor), kt.arguments.map { it.toProjection() }) - - private tailrec suspend fun buildAncestryInformation( - supertypes: Collection, - level: Int = 0, - ancestryInformation: Set = emptySet() - ): Set { - if (supertypes.isEmpty()) return ancestryInformation - - val (interfaces, superclass) = supertypes + private suspend fun buildAncestryInformation( + kotlinType: KotlinType + ): AncestryNode { + val (interfaces, superclass) = kotlinType.immediateSupertypes().filterNot { it.isAnyOrNullableAny() } .partition { val declaration = it.constructor.declarationDescriptor val descriptor = declaration as? ClassDescriptor @@ -775,25 +770,20 @@ private class DokkaDescriptorVisitor( descriptor?.kind == ClassKind.INTERFACE } - val updated = coroutineScope { - ancestryInformation + AncestryLevel( - level, - superclass.parallelMap(::toTypeConstructor).singleOrNull(), - interfaces.parallelMap(::toTypeConstructor) + return coroutineScope { + AncestryNode( + typeConstructor = toTypeConstructor(kotlinType), + superclass = superclass.parallelMap(::buildAncestryInformation).singleOrNull(), + interfaces = interfaces.parallelMap(::buildAncestryInformation) ) } - - return buildAncestryInformation( - supertypes = supertypes.flatMap { it.immediateSupertypes() }, - level = level + 1, - ancestryInformation = updated - ) } + private suspend fun ClassDescriptor.resolveClassDescriptionData(): ClassInfo { return coroutineScope { ClassInfo( - buildAncestryInformation(this@resolveClassDescriptionData.typeConstructor.supertypes.filterNot { it.isAnyOrNullableAny() }).sortedBy { it.level }, + buildAncestryInformation(this@resolveClassDescriptionData.defaultType), resolveDescriptorData() ) } @@ -1050,22 +1040,16 @@ private class DokkaDescriptorVisitor( else -> node?.text?.let { ComplexExpression(it) } } - private data class ClassInfo(val ancestry: List, val docs: SourceSetDependent) { + private data class ClassInfo(val ancestry: AncestryNode, val docs: SourceSetDependent) { val supertypes: List - get() = ancestry.firstOrNull { it.level == 0 }?.let { - listOfNotNull(it.superclass?.let { + get() = listOfNotNull(ancestry.superclass?.let { + it.typeConstructor.let { TypeConstructorWithKind( it, KotlinClassKindTypes.CLASS ) - }) + it.interfaces.map { TypeConstructorWithKind(it, KotlinClassKindTypes.INTERFACE) } - }.orEmpty() - - val allImplementedInterfaces: List - get() = ancestry.flatMap { it.interfaces }.distinct() - - val exceptionsInSupertypes: List? - get() = ancestry.exceptionsInSupertypes() + } + }) + ancestry.interfaces.map { TypeConstructorWithKind(it.typeConstructor, KotlinClassKindTypes.INTERFACE) } } private fun DescriptorVisibility.toDokkaVisibility(): org.jetbrains.dokka.model.Visibility = when (this.delegate) { @@ -1093,13 +1077,8 @@ private class DokkaDescriptorVisitor( kind == CallableMemberDescriptor.Kind.SYNTHESIZED || containingDeclaration.fqNameOrNull()?.asString() ?.let { it == "kotlin.Any" || it == "kotlin.Enum" || it == "java.lang.Enum" || it == "java.lang.Object" } == true -} -private data class AncestryLevel( - val level: Int, - val superclass: TypeConstructor?, - val interfaces: List -) + private fun AncestryNode.exceptionInSupertypesOrNull(): ExceptionInSupertypes? = + typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() }?.let { ExceptionInSupertypes(it.toSourceSetDependent()) } +} -private fun List.exceptionsInSupertypes(): List? = - mapNotNull { it.superclass }.filter { type -> type.dri.isDirectlyAnException() }.takeIf { it.isNotEmpty() } diff --git a/plugins/base/src/main/kotlin/translators/isException.kt b/plugins/base/src/main/kotlin/translators/isException.kt index d5b58445..d148cd34 100644 --- a/plugins/base/src/main/kotlin/translators/isException.kt +++ b/plugins/base/src/main/kotlin/translators/isException.kt @@ -1,6 +1,15 @@ package org.jetbrains.dokka.base.translators import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.AncestryNode +import org.jetbrains.dokka.model.TypeConstructor + +internal fun AncestryNode.typeConstructorsBeingExceptions(): List { + fun traverseSupertypes(ancestry: AncestryNode): List = + listOf(ancestry.typeConstructor) + (ancestry.superclass?.let(::traverseSupertypes) ?: emptyList()) + + return superclass?.let(::traverseSupertypes)?.filter { type -> type.dri.isDirectlyAnException() } ?: emptyList() +} internal fun DRI.isDirectlyAnException(): Boolean = toString().let { stringed -> diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index d912d65f..88c51b02 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -17,7 +17,7 @@ import org.jetbrains.dokka.analysis.KotlinAnalysis import org.jetbrains.dokka.analysis.PsiDocumentableSource import org.jetbrains.dokka.analysis.from import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.translators.isDirectlyAnException +import org.jetbrains.dokka.base.translators.typeConstructorsBeingExceptions import org.jetbrains.dokka.base.translators.psi.parsers.JavaDocumentationParser import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser import org.jetbrains.dokka.base.translators.unquotedValue @@ -29,25 +29,18 @@ 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 -import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.plugin import org.jetbrains.dokka.plugability.querySingle import org.jetbrains.dokka.transformers.sources.AsyncSourceToDocumentableTranslator -import org.jetbrains.dokka.transformers.sources.SourceToDocumentableTranslator import org.jetbrains.dokka.utilities.DokkaLogger 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.builtins.functions.FunctionClassDescriptor import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot -import org.jetbrains.kotlin.descriptors.Visibilities -import org.jetbrains.kotlin.descriptors.java.JavaVisibilities -import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName -import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName @@ -55,8 +48,6 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.psiUtil.getChildOfType import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance -import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.io.File @@ -178,28 +169,15 @@ class DefaultPsiToDocumentableTranslator( private suspend fun parseClasslike(psi: PsiClass, parent: DRI): DClasslike = coroutineScope { with(psi) { val dri = parent.withClass(name.toString()) - val ancestryTree = mutableListOf() val superMethodsKeys = hashSetOf() val superMethods = mutableListOf>() methods.asIterable().parallelForEach { superMethodsKeys.add(it.hash) } - fun parseSupertypes(superTypes: Array, level: Int = 0) { // TODO: Rewrite it - if (superTypes.isEmpty()) return - val parsedClasses = superTypes.filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi -> - supertypePsi.resolve()?.let { supertypePsiClass -> - val (supertypeDri, javaClassKind) = when { - supertypePsiClass.isInterface -> DRI.from(supertypePsiClass) to JavaClassKindTypes.INTERFACE - else -> DRI.from(supertypePsiClass) to JavaClassKindTypes.CLASS - } - GenericTypeConstructor( - supertypeDri, - supertypePsi.parameters.map(::getProjection) - ) to javaClassKind - } - } - val (classes, interfaces) = parsedClasses.partition { it.second == JavaClassKindTypes.CLASS } - ancestryTree.add(AncestryLevel(level, classes.firstOrNull()?.first, interfaces.map { it.first })) - superTypes.forEach { type -> + /** + * Caution! This method mutates superMethodsKeys and superMethods + */ + fun Array.getSuperTypesPsiClasses(): List> { + forEach { type -> (type as? PsiClassType)?.resolve()?.let { val definedAt = DRI.from(it) it.methods.forEach { method -> @@ -211,13 +189,36 @@ class DefaultPsiToDocumentableTranslator( superMethods.add(Pair(method, definedAt)) } } - parseSupertypes(it.superTypes, level + 1) } } + return filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi -> + supertypePsi.resolve()?.let { supertypePsiClass -> + val javaClassKind = when { + supertypePsiClass.isInterface -> JavaClassKindTypes.INTERFACE + else -> JavaClassKindTypes.CLASS + } + supertypePsiClass to javaClassKind + } + } + } + + fun traversePsiClassForAncestorsAndInheritedMembers(psiClass: PsiClass): AncestryNode { + val (classes, interfaces) = psiClass.superTypes.getSuperTypesPsiClasses() + .partition { it.second == JavaClassKindTypes.CLASS } + + return AncestryNode( + typeConstructor = GenericTypeConstructor( + DRI.from(psiClass), + psiClass.typeParameters.map(::getProjection) + ), + superclass = classes.singleOrNull()?.first?.let(::traversePsiClassForAncestorsAndInheritedMembers), + interfaces = interfaces.map { traversePsiClassForAncestorsAndInheritedMembers(it.first) } + ) } - parseSupertypes(superTypes) + + val ancestry: AncestryNode = traversePsiClassForAncestorsAndInheritedMembers(this) val (regularFunctions, accessors) = splitFunctionsAndAccessors() - val overriden = regularFunctions.flatMap { it.findSuperMethods().toList() } + val overridden = regularFunctions.flatMap { it.findSuperMethods().toList() } val documentation = javadocParser.parseDocumentation(this).toSourceSetDependent() val allFunctions = async { regularFunctions.parallelMapNotNull { @@ -225,27 +226,22 @@ class DefaultPsiToDocumentableTranslator( it, parentDRI = dri ) else null - } + superMethods.filter { it.first !in overriden }.parallelMap { parseFunction(it.first, inheritedFrom = it.second) } + } + 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 = ancestryTree.filter { it.level == 0 }.flatMap { - listOfNotNull(it.superclass?.let { - TypeConstructorWithKind( - typeConstructor = it, - kind = JavaClassKindTypes.CLASS - ) - }) + it.interfaces.map { + val ancestors = (listOfNotNull(ancestry.superclass?.let { + it.typeConstructor.let { TypeConstructorWithKind( - typeConstructor = it, - kind = JavaClassKindTypes.INTERFACE + it, + JavaClassKindTypes.CLASS ) } - }.toSourceSetDependent() + }) + ancestry.interfaces.map { TypeConstructorWithKind(it.typeConstructor, JavaClassKindTypes.INTERFACE) }).toSourceSetDependent() val modifiers = getModifier().toSourceSetDependent() val implementedInterfacesExtra = - ImplementedInterfaces(ancestryTree.flatMap { it.interfaces }.distinct().toSourceSetDependent()) + ImplementedInterfaces(ancestry.allImplementedInterfaces().toSourceSetDependent()) when { isAnnotationType -> DAnnotation( @@ -349,16 +345,15 @@ class DefaultPsiToDocumentableTranslator( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() .toAnnotations(), - isExceptionExtra(ancestryTree), + ancestry.exceptionInSupertypesOrNull() ) ) } } } - private fun isExceptionExtra(ancestryTree: List): ExceptionInSupertypes? = - ancestryTree.mapNotNull { it.superclass }.filter { it.dri.isDirectlyAnException() } - .takeIf { it.isNotEmpty() }?.let { ExceptionInSupertypes(it.toSourceSetDependent()) } + private fun AncestryNode.exceptionInSupertypesOrNull(): ExceptionInSupertypes? = + typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() }?.let { ExceptionInSupertypes(it.toSourceSetDependent()) } private fun parseFunction( psi: PsiMethod, @@ -436,53 +431,56 @@ class DefaultPsiToDocumentableTranslator( Annotations.Annotation(DRI("kotlin.jvm", "JvmStatic"), emptyMap()) } - private fun getBound(type: PsiType): Bound { - fun annotationsFromType(): List = type.annotations.toList().toListOfAnnotations() + private fun PsiTypeParameter.annotations(): PropertyContainer = this.annotations.toList().toListOfAnnotations().annotations() + private fun PsiType.annotations(): PropertyContainer = this.annotations.toList().toListOfAnnotations().annotations() - fun annotations(): PropertyContainer = - annotationsFromType().takeIf { it.isNotEmpty() }?.let { annotations -> - PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations()) - } ?: PropertyContainer.empty() + private fun List.annotations(): PropertyContainer = + this.takeIf { it.isNotEmpty() }?.let { annotations -> + PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations()) + } ?: PropertyContainer.empty() + private fun getProjection(type: PsiTypeParameter) = + TypeParameter( + dri = DRI.from(type), + name = type.name.orEmpty(), + extra = type.annotations() + ) + + private fun getBound(type: PsiType): Bound { fun bound() = when (type) { is PsiClassReferenceType -> type.resolve()?.let { resolved -> when { - resolved.qualifiedName == "java.lang.Object" -> JavaObject(annotations()) - resolved is PsiTypeParameter -> - TypeParameter( - dri = DRI.from(resolved), - name = resolved.name.orEmpty(), - extra = annotations() - ) + resolved.qualifiedName == "java.lang.Object" -> JavaObject(type.annotations()) + resolved is PsiTypeParameter -> getProjection(resolved) 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 = annotations() + extra = type.annotations() ) else -> GenericTypeConstructor( DRI.from(resolved), type.parameters.map { getProjection(it) }, - extra = annotations() + extra = type.annotations() ) } } ?: UnresolvedBound(type.presentableText) is PsiArrayType -> GenericTypeConstructor( DRI("kotlin", "Array"), listOf(getProjection(type.componentType)), - extra = annotations() + extra = type.annotations() ) is PsiPrimitiveType -> if (type.name == "void") Void else PrimitiveJavaType(type.name) - is PsiImmediateClassType -> JavaObject(annotations()) + is PsiImmediateClassType -> JavaObject(type.annotations()) else -> throw IllegalStateException("${type.presentableText} is not supported by PSI parser") } //We would like to cache most of the bounds since it is not common to annotate them, //but if this is the case, we treat them as 'one of' - return if (annotationsFromType().isEmpty()) { + return if (type.annotations.toList().toListOfAnnotations().isEmpty()) { cachedBounds.getOrPut(type.canonicalText) { bound() } @@ -657,10 +655,4 @@ class DefaultPsiToDocumentableTranslator( private val PsiElement.psiReference get() = getChildOfType()?.resolve() } - - private data class AncestryLevel( - val level: Int, - val superclass: TypeConstructor?, - val interfaces: List - ) } diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt index 1771595a..1d905e2d 100644 --- a/plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt +++ b/plugins/base/src/main/kotlin/translators/psi/parsers/PsiCommentsUtils.kt @@ -98,7 +98,7 @@ internal fun findClosestDocComment(element: PsiNamedElement, logger: DokkaLogger return findClosestDocComment(superMethods.single(), logger) } - val superMethodDocumentation = superMethods.map { method -> findClosestDocComment(method, logger) } + val superMethodDocumentation = superMethods.map { method -> findClosestDocComment(method, logger) }.distinct() if (superMethodDocumentation.size == 1) { return superMethodDocumentation.single() } @@ -143,4 +143,4 @@ internal fun PsiDocTag.contentElementsWithSiblingIfNeeded(): List = } internal fun PsiDocTag.resolveToElement(): PsiElement? = - dataElements.firstOrNull()?.firstChild?.referenceElementOrSelf()?.resolveToGetDri() \ No newline at end of file + dataElements.firstOrNull()?.firstChild?.referenceElementOrSelf()?.resolveToGetDri() -- cgit