Skip to content

Commit

Permalink
[K2] Fix missed annotations (#3715)
Browse files Browse the repository at this point in the history
  • Loading branch information
vmishenev authored Aug 6, 2024
1 parent dde362e commit dfbaf4a
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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) =
Expand Down Expand Up @@ -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? =
Expand Down
47 changes: 47 additions & 0 deletions dokka-subprojects/plugin-base/src/test/kotlin/model/TypesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -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<DProperty>()) {
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<DProperty>()) {
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
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -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")
}
}
}
Expand Down Expand Up @@ -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
)
}
}
}

}

0 comments on commit dfbaf4a

Please sign in to comment.