diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt index 6716fcf4ad..a99f8c066a 100644 --- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.analysis.api.symbols.KaPropertySymbol import org.jetbrains.kotlin.analysis.api.symbols.KaSymbol import org.jetbrains.kotlin.analysis.api.symbols.KaSymbolOrigin import org.jetbrains.kotlin.analysis.api.types.* +import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName @@ -48,7 +49,7 @@ internal class AnnotationTranslator { return directAnnotations + backingFieldAnnotations + fileLevelAnnotations } - private fun KaAnnotation.isNoExistedInSource() = psi == null + private fun KaAnnotation.isNoExistedInKotlinSource() = psi == null private fun AnnotationUseSiteTarget.toDokkaAnnotationScope(): Annotations.AnnotationScope = when (this) { AnnotationUseSiteTarget.PROPERTY_GETTER -> Annotations.AnnotationScope.DIRECT // due to compatibility with Dokka K1 AnnotationUseSiteTarget.PROPERTY_SETTER -> Annotations.AnnotationScope.DIRECT // due to compatibility with Dokka K1 @@ -57,10 +58,17 @@ internal class AnnotationTranslator { } private fun KaSession.mustBeDocumented(annotation: KaAnnotation): Boolean { - if (annotation.isNoExistedInSource()) return false + /** + * Do not document the synthetic [parameterNameAnnotation] annotation since Dokka K1 ignores it too. + * The annotation can be added by the compiler during "desugaring" functional types. + * e.g., `(x: Int) -> Unit` becomes `Function1<@ParameterName("x") Int, Unit>` + * @see ParameterName + * @see getPresentableName + */ + if(annotation.classId == parameterNameAnnotation && annotation.isNoExistedInKotlinSource()) return false + val annotationClass = findClass(annotation.classId ?: return false) - return annotationClass?.let { mustBeDocumentedAnnotation in it.annotations } - ?: false + return annotationClass?.let { mustBeDocumentedAnnotation in it.annotations } == true } private fun KaSession.toDokkaAnnotation(annotation: KaAnnotation) = @@ -132,11 +140,12 @@ internal class AnnotationTranslator { } companion object { - val mustBeDocumentedAnnotation = ClassId(FqName("kotlin.annotation"), FqName("MustBeDocumented"), false) - private val parameterNameAnnotation = ClassId(FqName("kotlin"), FqName("ParameterName"), false) + val mustBeDocumentedAnnotation = ClassId(StandardNames.ANNOTATION_PACKAGE_FQ_NAME, FqName("MustBeDocumented"), false) + val parameterNameAnnotation = StandardNames.FqNames.parameterNameClassId /** * Functional types can have **generated** [ParameterName] annotation + * e.g., `(x: Int) -> Unit` becomes `Function1<@ParameterName("x") Int, Unit>`. * @see ParameterName */ internal fun KaAnnotated.getPresentableName(): String? = diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/TypesTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/TypesTest.kt index bdb08fd84b..53be8736c1 100644 --- a/dokka-subprojects/plugin-base/src/test/kotlin/model/TypesTest.kt +++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/TypesTest.kt @@ -10,6 +10,7 @@ import org.jetbrains.dokka.model.* import utils.AbstractModelTest import utils.OnlyDescriptors import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertTrue class TypesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "types") { @@ -70,4 +71,50 @@ class TypesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "types") } } } + + @Test + fun `function types with a parameter name should have implicit @ParameterName annotations with mustBeDocumented=false`() { + inlineModelTest( + """ + |val nF: (param: Int) -> String = { _ -> "" }""" + ) { + with((this / "types" / "nF").cast()) { + assertTrue(type is FunctionalTypeConstructor) + val parameterType = + ((type as FunctionalTypeConstructor).projections[0] as Invariance<*>).inner as GenericTypeConstructor + val annotation = parameterType.extra[Annotations]?.directAnnotations?.values?.single()?.single() + assertEquals( + Annotations.Annotation( + dri = DRI("kotlin", "ParameterName"), + params = mapOf("name" to StringValue("param")), + mustBeDocumented = false + ), + annotation + ) + } + } + } + + @Test + fun `explicit @ParameterName annotations should have mustBeDocumented=true`() { + inlineModelTest( + """ + |val nF: (@ParameterName(name="param") Int) -> String = { _ -> "" }""" + ) { + with((this / "types" / "nF").cast()) { + assertTrue(type is FunctionalTypeConstructor) + val parameterType = + ((type as FunctionalTypeConstructor).projections[0] as Invariance<*>).inner as GenericTypeConstructor + val annotation = parameterType.extra[Annotations]?.directAnnotations?.values?.single()?.single() + assertEquals( + Annotations.Annotation( + dri = DRI("kotlin", "ParameterName"), + params = mapOf("name" to StringValue("param")), + mustBeDocumented = true + ), + annotation + ) + } + } + } } diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsTest.kt index f1f29192b6..b56f45e3a3 100644 --- a/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsTest.kt +++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsTest.kt @@ -4,7 +4,9 @@ package model.annotations +import org.jetbrains.dokka.Platform import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* import translators.findClasslike import kotlin.test.* @@ -14,7 +16,9 @@ class JavaAnnotationsTest : BaseAbstractTest() { val configuration = dokkaConfiguration { sourceSets { sourceSet { - sourceRoots = listOf("src/main/java") + analysisPlatform = Platform.jvm.toString() + classpath += jvmStdlibPath!! + sourceRoots = listOf("src/main/java", "src/main/kotlin") } } } @@ -192,4 +196,62 @@ class JavaAnnotationsTest : BaseAbstractTest() { } } } + + @Test + fun `accessors of a synthetic property or java methods should have annotations`() { + testInline( + """ + |/src/main/java/annotation/JavaParent.java + |package annotation; + |import javax.management.DescriptorKey; + | + |public class JavaParent { + | private int context = 0; + | + | public int getContext() { + | return context; + | } + | + | @DescriptorKey(value = "") + | public void setContext(int context) { + | this.context = context; + | } + | @DescriptorKey(value = "") + | public void foo() { + | } + |} + | + |/src/main/kotlin/annotation/TestClass.kt + |package annotation + |class A : JavaParent() + """.trimIndent(), + configuration + ) { + documentablesTransformationStage = { module -> + val testClass = module.findClasslike("annotation", "A") as DClass + val setter = testClass.properties.find{ it.name == "context" }?.setter + assertNotNull(setter) + val annotation = setter.extra[Annotations]?.directAnnotations?.values?.single()?.single() + assertEquals( + Annotations.Annotation( + dri = DRI("javax.management", "DescriptorKey"), + params = mapOf("value" to StringValue("")), + mustBeDocumented = true + ), annotation + ) + + val member = testClass.functions.find{ it.name == "foo" } + assertNotNull(member) + val annotation2 = member.extra[Annotations]?.directAnnotations?.values?.single()?.single() + assertEquals( + Annotations.Annotation( + dri = DRI("javax.management", "DescriptorKey"), + params = mapOf("value" to StringValue("")), + mustBeDocumented = true + ), annotation2 + ) + } + } + } + }