diff options
8 files changed, 197 insertions, 24 deletions
diff --git a/core/src/main/kotlin/model/documentableProperties.kt b/core/src/main/kotlin/model/documentableProperties.kt index 2845c7fc..991f5311 100644 --- a/core/src/main/kotlin/model/documentableProperties.kt +++ b/core/src/main/kotlin/model/documentableProperties.kt @@ -25,3 +25,12 @@ data class ImplementedInterfaces(val interfaces: SourceSetDependent<List<TypeCon override val key: ExtraProperty.Key<Documentable, *> = ImplementedInterfaces } + +data class ExceptionInSupertypes(val exceptions: SourceSetDependent<List<TypeConstructor>>): ExtraProperty<Documentable> { + companion object : ExtraProperty.Key<Documentable, ExceptionInSupertypes> { + override fun mergeStrategyFor(left: ExceptionInSupertypes, right: ExceptionInSupertypes) = + MergeStrategy.Replace(ExceptionInSupertypes(left.exceptions + right.exceptions)) + } + + override val key: ExtraProperty.Key<Documentable, *> = ExceptionInSupertypes +}
\ No newline at end of file diff --git a/core/src/main/kotlin/model/properties/PropertyContainer.kt b/core/src/main/kotlin/model/properties/PropertyContainer.kt index 19189b39..162f1bc8 100644 --- a/core/src/main/kotlin/model/properties/PropertyContainer.kt +++ b/core/src/main/kotlin/model/properties/PropertyContainer.kt @@ -18,7 +18,7 @@ data class PropertyContainer<C : Any> internal constructor( companion object { fun <T : Any> empty(): PropertyContainer<T> = PropertyContainer(emptyMap()) - fun <T : Any> withAll(vararg extras: ExtraProperty<T>) = empty<T>().addAll(extras.toList()) + fun <T : Any> withAll(vararg extras: ExtraProperty<T>?) = empty<T>().addAll(extras.filterNotNull()) fun <T : Any> withAll(extras: Collection<ExtraProperty<T>>) = empty<T>().addAll(extras) } } diff --git a/plugins/base/src/main/kotlin/transformers/documentables/utils.kt b/plugins/base/src/main/kotlin/transformers/documentables/utils.kt index 6b87fe39..ecf75a1f 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/utils.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/utils.kt @@ -2,6 +2,7 @@ package org.jetbrains.dokka.base.transformers.documentables import org.jetbrains.dokka.model.Annotations import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.ExceptionInSupertypes import org.jetbrains.dokka.model.WithSupertypes import org.jetbrains.dokka.model.properties.WithExtraProperties @@ -16,9 +17,5 @@ val <T> T.deprecatedAnnotation where T : WithExtraProperties<out Documentable> } } -val WithSupertypes.isException: Boolean - get() = supertypes.values.flatten().any { - val dri = it.typeConstructor.dri.toString() - dri == "kotlin/Exception///PointingToDeclaration/" || - dri == "java.lang/Exception///PointingToDeclaration/" - }
\ No newline at end of file +val <T : WithExtraProperties<out Documentable>> T.isException: Boolean + get() = extra[ExceptionInSupertypes] != null
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt index fe0c090b..1a643624 100644 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt @@ -1,15 +1,14 @@ package org.jetbrains.dokka.base.translators.descriptors -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.withContext 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.base.parsers.MarkdownParser +import org.jetbrains.dokka.base.translators.isDirectlyAnException import org.jetbrains.dokka.links.* import org.jetbrains.dokka.links.Callable import org.jetbrains.dokka.model.* @@ -48,8 +47,9 @@ import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.resolve.source.KotlinSourceElement import org.jetbrains.kotlin.resolve.source.PsiSourceElement import org.jetbrains.kotlin.types.* +import org.jetbrains.kotlin.types.model.typeConstructor +import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny -import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.lang.IllegalStateException @@ -181,7 +181,8 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent()) + ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent()), + info.exceptionsInSupertypes?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }, ) ) } @@ -218,7 +219,8 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent()) + ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent()), + info.exceptionsInSupertypes?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }, ) ) } @@ -382,7 +384,8 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll<DClass>( descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), - ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent()) + ImplementedInterfaces(info.allImplementedInterfaces.toSourceSetDependent()), + info.exceptionsInSupertypes?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }, ) ) } @@ -613,6 +616,7 @@ private class DokkaDescriptorVisitor( with(descriptor) { coroutineScope { val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } } + val info = buildAncestryInformation(listOf(underlyingType)).sortedBy { it.level } DTypeAlias( dri = DRI.from(this@with), @@ -625,7 +629,8 @@ private class DokkaDescriptorVisitor( sourceSets = setOf(sourceSet), generics = generics.await(), extra = PropertyContainer.withAll( - descriptor.getAnnotations().toSourceSetDependent().toAnnotations() + descriptor.getAnnotations().toSourceSetDependent().toAnnotations(), + info.exceptionsInSupertypes()?.takeIf { it.isNotEmpty() }?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }, ) ) } @@ -724,7 +729,7 @@ private class DokkaDescriptorVisitor( } return buildAncestryInformation( - supertypes = supertypes.flatMap { it.supertypes() }, + supertypes = supertypes.flatMap { it.immediateSupertypes() }, level = level + 1, ancestryInformation = updated ) @@ -946,12 +951,6 @@ private class DokkaDescriptorVisitor( private fun ValueArgument.childrenAsText() = this.safeAs<KtValueArgument>()?.children?.map { it.text }.orEmpty() - private data class AncestryLevel( - val level: Int, - val superclass: TypeConstructor?, - val interfaces: List<TypeConstructor> - ) - private data class ClassInfo(val ancestry: List<AncestryLevel>, val docs: SourceSetDependent<DocumentationNode>) { val supertypes: List<TypeConstructorWithKind> get() = ancestry.firstOrNull { it.level == 0 }?.let { @@ -965,6 +964,9 @@ private class DokkaDescriptorVisitor( val allImplementedInterfaces: List<TypeConstructor> get() = ancestry.flatMap { it.interfaces }.distinct() + + val exceptionsInSupertypes: List<TypeConstructor>? + get() = ancestry.exceptionsInSupertypes() } private fun Visibility.toDokkaVisibility(): org.jetbrains.dokka.model.Visibility = when (this) { @@ -978,3 +980,12 @@ private class DokkaDescriptorVisitor( private fun ConstantsEnumValue.fullEnumEntryName() = "${this.enumClassId.relativeClassName.asString()}.${this.enumEntryName.identifier}" } + +private data class AncestryLevel( + val level: Int, + val superclass: TypeConstructor?, + val interfaces: List<TypeConstructor> +) + +private fun List<AncestryLevel>.exceptionsInSupertypes(): List<TypeConstructor>? = + 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 new file mode 100644 index 00000000..d5b58445 --- /dev/null +++ b/plugins/base/src/main/kotlin/translators/isException.kt @@ -0,0 +1,9 @@ +package org.jetbrains.dokka.base.translators + +import org.jetbrains.dokka.links.DRI + +internal fun DRI.isDirectlyAnException(): Boolean = + toString().let { stringed -> + stringed == "kotlin/Exception///PointingToDeclaration/" || + stringed == "java.lang/Exception///PointingToDeclaration/" + } diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index cbd02158..f46f11c9 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -7,13 +7,13 @@ import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.* import com.intellij.psi.impl.source.PsiClassReferenceType import com.intellij.psi.impl.source.PsiImmediateClassType -import com.intellij.psi.javadoc.PsiDocComment import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet import org.jetbrains.dokka.analysis.KotlinAnalysis import org.jetbrains.dokka.analysis.PsiDocumentableSource import org.jetbrains.dokka.analysis.from +import org.jetbrains.dokka.base.translators.isDirectlyAnException import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.nextTarget import org.jetbrains.dokka.links.withClass @@ -313,13 +313,18 @@ class DefaultPsiToDocumentableTranslator( PropertyContainer.withAll( implementedInterfacesExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() - .toAnnotations() + .toAnnotations(), + isExceptionExtra(ancestryTree), ) ) } } } + private fun isExceptionExtra(ancestryTree: List<AncestryLevel>): ExceptionInSupertypes? = + ancestryTree.mapNotNull { it.superclass }.filter { it.dri.isDirectlyAnException() } + .takeIf { it.isNotEmpty() }?.let { ExceptionInSupertypes(it.toSourceSetDependent()) } + private fun parseFunction( psi: PsiMethod, isConstructor: Boolean = false, diff --git a/plugins/base/src/test/kotlin/transformers/isExceptionTest.kt b/plugins/base/src/test/kotlin/transformers/isExceptionTest.kt new file mode 100644 index 00000000..131c7b77 --- /dev/null +++ b/plugins/base/src/test/kotlin/transformers/isExceptionTest.kt @@ -0,0 +1,142 @@ +package transformers + +import org.jetbrains.dokka.base.transformers.documentables.isException +import org.jetbrains.dokka.model.DClass +import org.jetbrains.dokka.model.DTypeAlias +import org.junit.jupiter.api.Test +import utils.AbstractModelTest + +class IsExceptionKotlinTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "classes") { + @Test + fun `isException should work for kotlin exception`(){ + inlineModelTest( + """ + |class ExampleException(): Exception()""" + ) { + with((this / "classes" / "ExampleException").cast<DClass>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should work for java exceptions`(){ + inlineModelTest( + """ + |class ExampleException(): java.lang.Exception()""" + ) { + with((this / "classes" / "ExampleException").cast<DClass>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should work for RuntimeException`(){ + inlineModelTest( + """ + |class ExampleException(reason: String): RuntimeException(reason)""" + ) { + with((this / "classes" / "ExampleException").cast<DClass>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should work if exception is typealiased`(){ + inlineModelTest( + """ + |typealias ExampleException = java.lang.Exception""" + ) { + with((this / "classes" / "ExampleException").cast<DTypeAlias>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should work if exception is extending a typaliased class`(){ + inlineModelTest( + """ + |class ExampleException(): Exception() + |typealias ExampleExceptionAlias = ExampleException""" + ) { + with((this / "classes" / "ExampleExceptionAlias").cast<DTypeAlias>()) { + name equals "ExampleExceptionAlias" + isException equals true + } + } + } + + @Test + fun `isException should return false for a basic class`(){ + inlineModelTest( + """ + |class NotAnException(): Serializable""" + ) { + with((this / "classes" / "NotAnException").cast<DClass>()) { + name equals "NotAnException" + isException equals false + } + } + } + + @Test + fun `isException should return false for a typealias`(){ + inlineModelTest( + """ + |typealias NotAnException = Serializable""" + ) { + with((this / "classes" / "NotAnException").cast<DTypeAlias>()) { + name equals "NotAnException" + isException equals false + } + } + } +} + +class IsExceptionJavaTest: AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { + @Test + fun `isException should work for java exceptions`(){ + inlineModelTest( + """ + |class ExampleException extends java.lang.Exception { }""" + ) { + with((this / "java" / "ExampleException").cast<DClass>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should work for RuntimeException`(){ + inlineModelTest( + """ + |class ExampleException extends java.lang.RuntimeException""" + ) { + with((this / "java" / "ExampleException").cast<DClass>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should return false for a basic class`(){ + inlineModelTest( + """ + |class NotAnException extends Serializable""" + ) { + with((this / "java" / "NotAnException").cast<DClass>()) { + name equals "NotAnException" + isException equals false + } + } + } +}
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt index a7e10bd5..d6a15865 100644 --- a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt @@ -141,7 +141,7 @@ object DeprecatedPageCreator : PageTransformer { it.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedEnumConstants) } node.takeIf { it.isDeprecated() }?.putAs( - if ((node.documentable as? WithSupertypes)?.isException == true) DeprecatedPageSection.DeprecatedExceptions + if ((node as? WithJavadocExtra<out Documentable>)?.isException == true) DeprecatedPageSection.DeprecatedExceptions else when (node.kind) { "enum" -> DeprecatedPageSection.DeprecatedEnums "interface" -> DeprecatedPageSection.DeprecatedInterfaces |