diff options
author | Marcin Aman <marcin.aman@gmail.com> | 2021-02-05 14:55:45 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-05 14:55:45 +0100 |
commit | 70000c87a37caa2a6b518a555f53c98514434403 (patch) | |
tree | 3a0e7a2b797b6edfffc0396bb5f9d35708120e9d /plugins/base | |
parent | b44e41ec8e3e26c73affaaa98bbd170fde352d96 (diff) | |
download | dokka-70000c87a37caa2a6b518a555f53c98514434403.tar.gz dokka-70000c87a37caa2a6b518a555f53c98514434403.tar.bz2 dokka-70000c87a37caa2a6b518a555f53c98514434403.zip |
Annotations for parameters (#1710)
* Annotations for parameters
* Annotations for parameters
Diffstat (limited to 'plugins/base')
8 files changed, 237 insertions, 76 deletions
diff --git a/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt b/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt index 3851b39c..a5aa6879 100644 --- a/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt +++ b/plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt @@ -6,25 +6,25 @@ import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.driOrNull import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.drisOfAllNestedBounds +import org.jetbrains.dokka.model.AnnotationTarget interface JvmSignatureUtils { - fun PageContentBuilder.DocumentableContentBuilder.annotationsBlock(d: Documentable) + fun PageContentBuilder.DocumentableContentBuilder.annotationsBlock(d: AnnotationTarget) - fun PageContentBuilder.DocumentableContentBuilder.annotationsInline(d: Documentable) + fun PageContentBuilder.DocumentableContentBuilder.annotationsInline(d: AnnotationTarget) fun <T : Documentable> WithExtraProperties<T>.modifiers(): SourceSetDependent<Set<ExtraModifiers>> fun Collection<ExtraModifiers>.toSignatureString(): String = joinToString("") { it.name.toLowerCase() + " " } - fun <T : Documentable> WithExtraProperties<T>.annotations(): SourceSetDependent<List<Annotations.Annotation>> = + fun <T : AnnotationTarget> WithExtraProperties<T>.annotations(): SourceSetDependent<List<Annotations.Annotation>> = extra[Annotations]?.directAnnotations ?: emptyMap() private fun PageContentBuilder.DocumentableContentBuilder.annotations( - d: Documentable, + d: AnnotationTarget, ignored: Set<Annotations.Annotation>, styles: Set<Style>, operation: PageContentBuilder.DocumentableContentBuilder.(Annotations.Annotation) -> Unit @@ -40,6 +40,10 @@ interface JvmSignatureUtils { is DEnumEntry -> d.annotations() is DTypeAlias -> d.annotations() is DParameter -> d.annotations() + is TypeParameter -> d.annotations() + is GenericTypeConstructor -> d.annotations() + is FunctionalTypeConstructor -> d.annotations() + is JavaObject -> d.annotations() else -> null }?.let { it.entries.forEach { @@ -104,7 +108,7 @@ interface JvmSignatureUtils { } fun PageContentBuilder.DocumentableContentBuilder.annotationsBlockWithIgnored( - d: Documentable, + d: AnnotationTarget, ignored: Set<Annotations.Annotation>, renderAtStrategy: AtStrategy, listBrackets: Pair<Char, Char>, @@ -118,7 +122,7 @@ interface JvmSignatureUtils { } fun PageContentBuilder.DocumentableContentBuilder.annotationsInlineWithIgnored( - d: Documentable, + d: AnnotationTarget, ignored: Set<Annotations.Annotation>, renderAtStrategy: AtStrategy, listBrackets: Pair<Char, Char>, diff --git a/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt b/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt index 7f29e032..4eac9d3f 100644 --- a/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt +++ b/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt @@ -164,6 +164,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog link(c.name!!, c.dri) if (c is WithGenerics) { list(c.generics, prefix = "<", suffix = ">") { + annotationsInline(it) +buildSignature(it) } } @@ -219,6 +220,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog text(p.modifiers()[it]?.toSignatureString() ?: "") p.setter?.let { text("var ") } ?: text("val ") list(p.generics, prefix = "<", suffix = "> ") { + annotationsInline(it) +buildSignature(it) } p.receiver?.also { @@ -252,6 +254,7 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog text("fun ") val usedGenerics = if (f.isConstructor) f.generics.filter { f uses it } else f.generics list(usedGenerics, prefix = "<", suffix = "> ") { + annotationsInline(it) +buildSignature(it) } f.receiver?.also { @@ -328,19 +331,23 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog private fun PageContentBuilder.DocumentableContentBuilder.signatureForProjection( p: Projection, showFullyQualifiedName: Boolean = false - ): Unit = - when (p) { - is TypeParameter -> link(p.name, p.dri) - - is FunctionalTypeConstructor -> + ): Unit { + return when (p) { + is TypeParameter -> { + annotationsInline(p) + link(p.name, p.dri) + } + is FunctionalTypeConstructor -> { + annotationsInline(p) +funType(mainDRI.single(), mainSourcesetData, p) - + } is GenericTypeConstructor -> group(styles = emptySet()) { val linkText = if (showFullyQualifiedName && p.dri.packageName != null) { "${p.dri.packageName}.${p.dri.classNames.orEmpty()}" } else p.dri.classNames.orEmpty() if (p.presentableName != null) text(p.presentableName + ": ") + annotationsInline(p) link(linkText, p.dri) list(p.projections, prefix = "<", suffix = ">") { signatureForProjection(it, showFullyQualifiedName) @@ -360,12 +367,16 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog } is TypeAliased -> signatureForProjection(p.typeAlias) - is JavaObject -> link("Any", DriOfAny) + is JavaObject -> { + annotationsInline(p) + link("Any", DriOfAny) + } is Void -> link("Unit", DriOfUnit) is PrimitiveJavaType -> signatureForProjection(p.translateToKotlin(), showFullyQualifiedName) is Dynamic -> text("dynamic") is UnresolvedBound -> text(p.name) } + } private fun funType(dri: DRI, sourceSets: Set<DokkaSourceSet>, type: FunctionalTypeConstructor) = contentBuilder.contentFor(dri, sourceSets, ContentKind.Main) { diff --git a/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt b/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt index 2266b691..bb350b34 100644 --- a/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt +++ b/plugins/base/src/main/kotlin/signatures/KotlinSignatureUtils.kt @@ -5,6 +5,7 @@ import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.DriOfAny import org.jetbrains.dokka.links.DriOfUnit import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.AnnotationTarget import org.jetbrains.dokka.model.properties.WithExtraProperties object KotlinSignatureUtils : JvmSignatureUtils { @@ -18,10 +19,10 @@ object KotlinSignatureUtils : JvmSignatureUtils { ) - override fun PageContentBuilder.DocumentableContentBuilder.annotationsBlock(d: Documentable) = + override fun PageContentBuilder.DocumentableContentBuilder.annotationsBlock(d: AnnotationTarget) = annotationsBlockWithIgnored(d, ignoredAnnotations, strategy, listBrackets, classExtension) - override fun PageContentBuilder.DocumentableContentBuilder.annotationsInline(d: Documentable) = + override fun PageContentBuilder.DocumentableContentBuilder.annotationsInline(d: AnnotationTarget) = annotationsInlineWithIgnored(d, ignoredAnnotations, strategy, listBrackets, classExtension) override fun <T : Documentable> WithExtraProperties<T>.modifiers() = diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt index 18231601..51389f37 100644 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt @@ -14,6 +14,7 @@ import org.jetbrains.dokka.base.translators.unquotedValue import org.jetbrains.dokka.links.* 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.* @@ -114,7 +115,7 @@ private class DokkaDescriptorVisitor( } } - private fun <T> T.toSourceSetDependent() = if(this != null) mapOf(sourceSet to this) else emptyMap() + private fun <T> T.toSourceSetDependent() = if (this != null) mapOf(sourceSet to this) else emptyMap() suspend fun visitPackageFragmentDescriptor( descriptor: PackageFragmentDescriptor, @@ -489,7 +490,8 @@ private class DokkaDescriptorVisitor( extra = PropertyContainer.withAll( InheritedMember(inheritedFrom.toSourceSetDependent()), descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(), - (descriptor.getAnnotations() + descriptor.fileLevelAnnotations()).toSourceSetDependent().toAnnotations(), + (descriptor.getAnnotations() + descriptor.fileLevelAnnotations()).toSourceSetDependent() + .toAnnotations(), ) ) } @@ -784,36 +786,45 @@ private class DokkaDescriptorVisitor( ) private suspend fun org.jetbrains.kotlin.descriptors.annotations.Annotations.getPresentableName(): String? = - map { it.toAnnotation() }.singleOrNull { it.dri.classNames == "ParameterName" }?.params?.get("name") + mapNotNull { it.toAnnotation() }.singleOrNull { it.dri.classNames == "ParameterName" }?.params?.get("name") .safeAs<StringValue>()?.value?.let { unquotedValue(it) } - private suspend fun KotlinType.toBound(): Bound = when (this) { - - is DynamicType -> Dynamic - is AbbreviatedType -> TypeAliased( - abbreviation.toBound(), - expandedType.toBound() - ) - else -> when (val ctor = constructor.declarationDescriptor) { - is TypeParameterDescriptor -> TypeParameter( - dri = DRI.from(ctor), - name = ctor.name.asString(), - presentableName = annotations.getPresentableName() - ) - is FunctionClassDescriptor -> FunctionalTypeConstructor( - DRI.from(ctor), - arguments.map { it.toProjection() }, - isExtensionFunction = isExtensionFunctionType || isBuiltinExtensionFunctionalType, - isSuspendable = isSuspendFunctionTypeOrSubtype, - presentableName = annotations.getPresentableName() + private suspend fun KotlinType.toBound(): Bound { + suspend fun <T : AnnotationTarget> annotations(): PropertyContainer<T> = + getAnnotations().takeIf { it.isNotEmpty() }?.let { annotations -> + PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations()) + } ?: PropertyContainer.empty() + + return when (this) { + is DynamicType -> Dynamic + is AbbreviatedType -> TypeAliased( + abbreviation.toBound(), + expandedType.toBound() ) - else -> GenericTypeConstructor( - DRI.from(ctor!!), // TODO: remove '!!' - arguments.map { it.toProjection() }, - annotations.getPresentableName() - ) - }.let { - if (isMarkedNullable) Nullable(it) else it + else -> when (val ctor = constructor.declarationDescriptor) { + is TypeParameterDescriptor -> TypeParameter( + dri = DRI.from(ctor), + name = ctor.name.asString(), + presentableName = annotations.getPresentableName(), + extra = annotations() + ) + is FunctionClassDescriptor -> FunctionalTypeConstructor( + DRI.from(ctor), + arguments.map { it.toProjection() }, + isExtensionFunction = isExtensionFunctionType || isBuiltinExtensionFunctionalType, + isSuspendable = isSuspendFunctionTypeOrSubtype, + presentableName = annotations.getPresentableName(), + extra = annotations() + ) + else -> GenericTypeConstructor( + DRI.from(ctor!!), // TODO: remove '!!' + arguments.map { it.toProjection() }, + annotations.getPresentableName(), + extra = annotations() + ) + }.let { + if (isMarkedNullable) Nullable(it) else it + } } } @@ -823,7 +834,7 @@ private class DokkaDescriptorVisitor( private suspend fun TypeProjection.formPossiblyVariant(): Projection = type.toBound().wrapWithVariance(projectionKind) - private suspend fun TypeParameterDescriptor.variantTypeParameter(type: TypeParameter) = + private fun TypeParameterDescriptor.variantTypeParameter(type: TypeParameter) = type.wrapWithVariance(variance) private fun <T : Bound> T.wrapWithVariance(variance: org.jetbrains.kotlin.types.Variance) = @@ -909,7 +920,7 @@ private class DokkaDescriptorVisitor( private suspend fun Annotated.getAnnotations() = annotations.parallelMapNotNull { it.toAnnotation() } - private suspend fun ConstantValue<*>.toValue(): AnnotationParameterValue? = when (this) { + private fun ConstantValue<*>.toValue(): AnnotationParameterValue? = when (this) { is ConstantsAnnotationValue -> value.toAnnotation()?.let { AnnotationValue(it) } is ConstantsArrayValue -> ArrayValue(value.mapNotNull { it.toValue() }) is ConstantsEnumValue -> EnumValue( @@ -933,7 +944,7 @@ private class DokkaDescriptorVisitor( else -> StringValue(unquotedValue(toString())) } - private suspend fun AnnotationDescriptor.toAnnotation(scope: Annotations.AnnotationScope = Annotations.AnnotationScope.DIRECT): Annotations.Annotation = + private fun AnnotationDescriptor.toAnnotation(scope: Annotations.AnnotationScope = Annotations.AnnotationScope.DIRECT): Annotations.Annotation = Annotations.Annotation( DRI.from(annotationClass as DeclarationDescriptor), allValueArguments.map { it.key.asString() to it.value.toValue() }.filter { @@ -944,7 +955,8 @@ private class DokkaDescriptorVisitor( ) private fun AnnotationDescriptor.mustBeDocumented(): Boolean = - annotationClass?.annotations?.hasAnnotation(FqName("kotlin.annotation.MustBeDocumented")) ?: false + if (source.toString() == "NO_SOURCE") false + else annotationClass?.annotations?.hasAnnotation(FqName("kotlin.annotation.MustBeDocumented")) ?: false private suspend fun PropertyDescriptor.getAnnotationsWithBackingField(): List<Annotations.Annotation> = getAnnotations() + (backingField?.getAnnotations() ?: emptyList()) @@ -1010,13 +1022,14 @@ private class DokkaDescriptorVisitor( private fun ConstantsEnumValue.fullEnumEntryName() = "${this.enumClassId.relativeClassName.asString()}.${this.enumEntryName.identifier}" - private fun DeclarationDescriptorWithSource.ktFile(): KtFile? = (source.containingFile as? PsiSourceFile)?.psiFile as? KtFile + private fun DeclarationDescriptorWithSource.ktFile(): KtFile? = + (source.containingFile as? PsiSourceFile)?.psiFile as? KtFile private suspend fun DeclarationDescriptorWithSource.fileLevelAnnotations() = ktFile() ?.let { file -> resolutionFacade.resolveSession.getFileAnnotations(file) } ?.toList() + ?.parallelMap { it.toAnnotation(scope = Annotations.AnnotationScope.FILE) } .orEmpty() - .parallelMap { it.toAnnotation(scope = Annotations.AnnotationScope.FILE) } } private data class AncestryLevel( diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index 5b913dcb..c09d1156 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -22,9 +22,11 @@ import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.nextTarget import org.jetbrains.dokka.links.withClass import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.AnnotationTarget 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 @@ -200,8 +202,15 @@ class DefaultPsiToDocumentableTranslator( parseSupertypes(superTypes) val (regularFunctions, accessors) = splitFunctionsAndAccessors() val documentation = javadocParser.parseDocumentation(this).toSourceSetDependent() - val allFunctions = async { regularFunctions.parallelMapNotNull { if (!it.isConstructor) parseFunction(it, parentDRI = dri) else null } + - superMethods.parallelMap { parseFunction(it.first, inheritedFrom = it.second) } } + val allFunctions = async { + regularFunctions.parallelMapNotNull { + if (!it.isConstructor) parseFunction( + it, + parentDRI = dri + ) else null + } + + superMethods.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() @@ -406,40 +415,61 @@ class DefaultPsiToDocumentableTranslator( Annotations.Annotation(DRI("kotlin.jvm", "JvmStatic"), emptyMap()) } - private fun getBound(type: PsiType): Bound = - cachedBounds.getOrPut(type.canonicalText) { - when (type) { - is PsiClassReferenceType -> { - val resolved: PsiClass = type.resolve() - ?: return UnresolvedBound(type.presentableText) + private fun getBound(type: PsiType): Bound { + fun annotationsFromType(): List<Annotations.Annotation> = type.annotations.toList().toListOfAnnotations() + + fun <T : AnnotationTarget> annotations(): PropertyContainer<T> = + annotationsFromType().takeIf { it.isNotEmpty() }?.let { annotations -> + PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations()) + } ?: PropertyContainer.empty() + + fun bound() = when (type) { + is PsiClassReferenceType -> + type.resolve()?.let { resolved -> when { - resolved.qualifiedName == "java.lang.Object" -> JavaObject + resolved.qualifiedName == "java.lang.Object" -> JavaObject(annotations()) resolved is PsiTypeParameter -> TypeParameter( dri = DRI.from(resolved), - name = resolved.name.orEmpty() + name = resolved.name.orEmpty(), + extra = 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) } + type.parameters.map { getProjection(it) }, + extra = annotations() ) else -> GenericTypeConstructor( DRI.from(resolved), - type.parameters.map { getProjection(it) }) + type.parameters.map { getProjection(it) }, + extra = annotations() + ) } - } - is PsiArrayType -> GenericTypeConstructor( - DRI("kotlin", "Array"), - listOf(getProjection(type.componentType)) - ) - is PsiPrimitiveType -> if (type.name == "void") Void else PrimitiveJavaType(type.name) - is PsiImmediateClassType -> JavaObject - else -> throw IllegalStateException("${type.presentableText} is not supported by PSI parser") + } ?: UnresolvedBound(type.presentableText) + is PsiArrayType -> GenericTypeConstructor( + DRI("kotlin", "Array"), + listOf(getProjection(type.componentType)), + extra = annotations() + ) + is PsiPrimitiveType -> if (type.name == "void") Void else PrimitiveJavaType(type.name) + is PsiImmediateClassType -> JavaObject(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()) { + cachedBounds.getOrPut(type.canonicalText) { + bound() } + } else { + bound() } + } + private fun getVariance(type: PsiWildcardType): Projection = when { type.extendsBound != PsiType.NULL -> Covariance(getBound(type.extendsBound)) diff --git a/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt b/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt index 164e5f4a..0b9e2390 100644 --- a/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt +++ b/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt @@ -5,11 +5,15 @@ 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.StringValue +import org.jetbrains.dokka.model.dfs import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.ContentText +import org.jetbrains.dokka.pages.MemberPageNode import org.jetbrains.dokka.pages.PackagePageNode import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult import org.junit.jupiter.api.Test import utils.ParamAttributes +import utils.assertNotNull import utils.bareSignature import utils.propertySignature import kotlin.test.assertEquals @@ -227,7 +231,7 @@ class ContentForAnnotationsTest : BaseAbstractTest() { } @Test - fun `JvmName for property with setter and getter`(){ + fun `JvmName for property with setter and getter`() { testInline( """ |/src/main/kotlin/test/source.kt @@ -237,7 +241,8 @@ class ContentForAnnotationsTest : BaseAbstractTest() { |var property: String | get() = "" | set(value) {} - """.trimIndent(), testConfiguration) { + """.trimIndent(), testConfiguration + ) { documentablesCreationStage = { modules -> fun expectedAnnotation(name: String) = Annotations.Annotation( dri = DRI("kotlin.jvm", "JvmName"), @@ -264,4 +269,49 @@ class ContentForAnnotationsTest : BaseAbstractTest() { } } } + + @Test + fun `annotated bounds in Kotlin`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |@MustBeDocumented + |@Target(AnnotationTarget.TYPE_PARAMETER) + |annotation class Hello(val bar: String) + |fun <T: @Hello("abc") String> foo(arg: String): List<T> = TODO() + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { root -> + val fooPage = root.dfs { it.name == "foo" } as MemberPageNode + fooPage.content.dfs { it is ContentText && it.text == "Hello" }.assertNotNull() + } + } + } + + @Test + fun `annotated bounds in Java`() { + testInline( + """ + |/src/main/java/demo/AnnotationTest.java + |package demo; + |import java.lang.annotation.*; + |import java.util.List; + |@Documented + |@Target({ElementType.TYPE_USE, ElementType.TYPE}) + |@interface Hello { + | public String bar() default ""; + |} + |public class AnnotationTest { + | public <T extends @Hello(bar = "baz") String> List<T> foo() { + | return null; + | } + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { root -> + val fooPage = root.dfs { it.name == "foo" } as MemberPageNode + fooPage.content.dfs { it is ContentText && it.text == "Hello" }.assertNotNull() + } + } + } } diff --git a/plugins/base/src/test/kotlin/model/annotations/JavaAnnotationsForParametersTest.kt b/plugins/base/src/test/kotlin/model/annotations/JavaAnnotationsForParametersTest.kt index 5679010d..9e00fafd 100644 --- a/plugins/base/src/test/kotlin/model/annotations/JavaAnnotationsForParametersTest.kt +++ b/plugins/base/src/test/kotlin/model/annotations/JavaAnnotationsForParametersTest.kt @@ -1,11 +1,10 @@ package model.annotations import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.Annotations -import org.jetbrains.dokka.model.DClass -import org.jetbrains.dokka.model.DFunction +import org.jetbrains.dokka.model.* import org.junit.jupiter.api.Test import utils.AbstractModelTest +import kotlin.test.Ignore import kotlin.test.assertEquals class JavaAnnotationsForParametersTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { @@ -85,4 +84,34 @@ class JavaAnnotationsForParametersTest : AbstractModelTest("/src/main/kotlin/jav } } } + + @Test + fun `function with generic parameter that has annotated bounds`() { + inlineModelTest( + """ + |@Retention(RetentionPolicy.RUNTIME) + |@Target({ElementType.TYPE_USE}) + |@interface Hello { + | public String bar() default ""; + |} + |class Test { + | public <T extends @Hello(bar = "baz") String> List<T> foo() { + | return null; + | } + |} + """.trimIndent() + ) { + with((this / "java" / "Test").cast<DClass>()) { + with((this / "foo").cast<DFunction>()) { + val annotations = ((generics.first().bounds.first() as Nullable).inner as GenericTypeConstructor) + .extra[Annotations]?.directAnnotations?.flatMap { it.value } + val driOfHello = DRI("java", "Hello") + val annotationsValues = annotations?.flatMap { it.params.values }?.map { it.toString() }?.toList() + + assertEquals(listOf(driOfHello), annotations?.map { it.dri }) + assertEquals(listOf("baz"), annotationsValues) + } + } + } + } }
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/model/annotations/KotlinAnnotationsForParametersTest.kt b/plugins/base/src/test/kotlin/model/annotations/KotlinAnnotationsForParametersTest.kt index 4cc34c09..e88d7353 100644 --- a/plugins/base/src/test/kotlin/model/annotations/KotlinAnnotationsForParametersTest.kt +++ b/plugins/base/src/test/kotlin/model/annotations/KotlinAnnotationsForParametersTest.kt @@ -3,6 +3,8 @@ package model.annotations import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.Annotations import org.jetbrains.dokka.model.DFunction +import org.jetbrains.dokka.model.DProperty +import org.jetbrains.dokka.model.GenericTypeConstructor import org.junit.jupiter.api.Test import utils.AbstractModelTest import kotlin.test.assertEquals @@ -27,4 +29,25 @@ class KotlinAnnotationsForParametersTest : AbstractModelTest("/src/main/kotlin/a } } } + + @Test + fun `generic receiver with annotated bounds`() { + inlineModelTest( + """ + |@Target(AnnotationTarget.TYPE_PARAMETER) + |annotation class Hello(val bar: String) + |fun <T: @Hello("abc") String> foo(arg: String): List<T> = TODO() + """.trimIndent() + ) { + with((this / "annotations" / "foo").cast<DFunction>()) { + val annotations = (generics.first().bounds.first() as GenericTypeConstructor) + .extra[Annotations]?.directAnnotations?.flatMap { it.value } + val driOfHello = DRI("annotations", "Hello") + val annotationsValues = annotations?.flatMap { it.params.values }?.map { it.toString() }?.toList() + + assertEquals(listOf(driOfHello), annotations?.map { it.dri }) + assertEquals(listOf("abc"), annotationsValues) + } + } + } }
\ No newline at end of file |