From 75e9f34ef89ae7840dc64b8e2da115818b1e4b3a Mon Sep 17 00:00:00 2001 From: IgnatBeresnev Date: Thu, 1 Sep 2022 01:23:23 +0200 Subject: [PATCH 1/8] Add documentation for synthetic Enum `values()` and `valueOf()` functions --- ...faultDescriptorToDocumentableTranslator.kt | 14 ++- ...thesizedDescriptorDocumentationProvider.kt | 62 +++++++++++++ .../psi/DefaultPsiToDocumentableTranslator.kt | 11 ++- .../SynheticElementDocumentationProvider.kt | 89 +++++++++++++++++++ ...tDescriptorToDocumentableTranslatorTest.kt | 60 ++++++++++++- .../DefaultPsiToDocumentableTranslatorTest.kt | 57 ++++++++++++ 6 files changed, 285 insertions(+), 8 deletions(-) create mode 100644 plugins/base/src/main/kotlin/translators/descriptors/SynthesizedDescriptorDocumentationProvider.kt create mode 100644 plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt index c52fbd379f..607218dc2e 100644 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt @@ -577,8 +577,7 @@ private class DokkaDescriptorVisitor( sources = actual, visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), generics = generics.await(), - documentation = descriptor.takeIf { it.kind != CallableMemberDescriptor.Kind.SYNTHESIZED } - ?.resolveDescriptorData() ?: emptyMap(), + documentation = descriptor.getDocumentation(), modifier = descriptor.modifier().toSourceSetDependent(), type = descriptor.returnType!!.toBound(), sourceSets = setOf(sourceSet), @@ -594,6 +593,15 @@ private class DokkaDescriptorVisitor( } } + private fun FunctionDescriptor.getDocumentation(): SourceSetDependent { + val isSynthesized = this.kind == CallableMemberDescriptor.Kind.SYNTHESIZED + return if (isSynthesized) { + this.getSyntheticFunctionDocumentation()?.toSourceSetDependent() ?: emptyMap() + } else { + this.resolveDescriptorData() + } + } + /** * `createDRI` returns the DRI of the exact element and potential DRI of an element that is overriding it * (It can be also FAKE_OVERRIDE which is in fact just inheritance of the symbol) @@ -609,7 +617,7 @@ private class DokkaDescriptorVisitor( private fun FunctionDescriptor.isObvious(): Boolean { return kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE - || kind == CallableMemberDescriptor.Kind.SYNTHESIZED + || (kind == CallableMemberDescriptor.Kind.SYNTHESIZED && !this.isDocumentedSyntheticFunction()) || containingDeclaration.fqNameOrNull()?.isObvious() == true } diff --git a/plugins/base/src/main/kotlin/translators/descriptors/SynthesizedDescriptorDocumentationProvider.kt b/plugins/base/src/main/kotlin/translators/descriptors/SynthesizedDescriptorDocumentationProvider.kt new file mode 100644 index 0000000000..1395242b0a --- /dev/null +++ b/plugins/base/src/main/kotlin/translators/descriptors/SynthesizedDescriptorDocumentationProvider.kt @@ -0,0 +1,62 @@ +package org.jetbrains.dokka.base.translators.descriptors + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.PointingToDeclaration +import org.jetbrains.dokka.model.doc.* +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.resolve.DescriptorFactory + +/** + * Copy-pasted from kotlin stdlib sources, it should be present in Enum.kt file + * + * See https://github.com/JetBrains/kotlin/blob/master/core/builtins/native/kotlin/Enum.kt + */ +internal val KOTLIN_ENUM_VALUE_OF_DOCUMENTATION = DocumentationNode(listOf( + Description( + P(listOf( + Text( + "Returns the enum constant of this type with the specified name. " + + "The string must match exactly an identifier used to declare an enum " + + "constant in this type. (Extraneous whitespace characters are not permitted.)" + ) + )) + ), + Throws( + name = "kotlin.IllegalArgumentException", + exceptionAddress = DRI( + packageName = "kotlin", + classNames = "IllegalArgumentException", + target = PointingToDeclaration + ), + root = P(listOf( + Text("if this enum type has no constant with the specified name") + )) + ) +)) + +/** + * Copy-pasted from kotlin stdlib sources, it should be present in Enum.kt file + * + * See https://github.com/JetBrains/kotlin/blob/master/core/builtins/native/kotlin/Enum.kt + */ +internal val KOTLIN_ENUM_VALUES_DOCUMENTATION = DocumentationNode(listOf( + Description( + P(listOf( + Text( + "Returns an array containing the constants of this enum type, in the order " + + "they're declared. This method may be used to iterate over the constants." + ) + )) + ) +)) + +internal fun FunctionDescriptor.isDocumentedSyntheticFunction() = + DescriptorFactory.isEnumValuesMethod(this) || DescriptorFactory.isEnumValueOfMethod(this) + +internal fun FunctionDescriptor.getSyntheticFunctionDocumentation(): DocumentationNode? { + return when { + DescriptorFactory.isEnumValuesMethod(this) -> KOTLIN_ENUM_VALUES_DOCUMENTATION + DescriptorFactory.isEnumValueOfMethod(this) -> KOTLIN_ENUM_VALUE_OF_DOCUMENTATION + else -> null + } +} diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index 585244796a..988098cd7b 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -8,8 +8,6 @@ import com.intellij.lang.jvm.annotation.JvmAnnotationEnumFieldValue import com.intellij.lang.jvm.types.JvmReferenceType 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 kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet @@ -404,7 +402,7 @@ class DefaultPsiToDocumentableTranslator( val dri = parentDRI?.let { dri -> DRI.from(psi).copy(packageName = dri.packageName, classNames = dri.classNames) } ?: DRI.from(psi) - val docs = javadocParser.parseDocumentation(psi) + val docs = psi.getDocumentation() return DFunction( dri = dri, name = psi.name, @@ -452,8 +450,13 @@ class DefaultPsiToDocumentableTranslator( ) } + private fun PsiMethod.getDocumentation(): DocumentationNode = + this.takeIf { it is SyntheticElement }?.getSyntheticMethodDocumentation() + ?: javadocParser.parseDocumentation(this) + private fun PsiMethod.isObvious(inheritedFrom: DRI? = null): Boolean { - return this is SyntheticElement || inheritedFrom?.isObvious() == true + return (this is SyntheticElement && !this.isDocumentedSyntheticMethod()) + || inheritedFrom?.isObvious() == true } private fun DRI.isObvious(): Boolean { diff --git a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt new file mode 100644 index 0000000000..f50a046c53 --- /dev/null +++ b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt @@ -0,0 +1,89 @@ +package org.jetbrains.dokka.base.translators.psi + +import com.intellij.psi.PsiMethod +import com.intellij.psi.SyntheticElement +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.PointingToDeclaration +import org.jetbrains.dokka.model.doc.* + +/** + * Adaptation of text taken from [java.lang.Enum.valueOf] + */ +internal val JAVA_ENUM_VALUE_OF_DOCUMENTATION = DocumentationNode(listOf( + Description( + P(listOf( + Text( + "Returns the enum constant of the this enum type with the " + + "specified name. The name must match exactly an identifier used " + + "to declare an enum constant in this type. (Extraneous whitespace " + + "characters are not permitted.)" + ) + )) + ), + Param( + name = "name", + root = P(listOf( + Text("the name of the constant to return") + )) + ), + Return(root = P(listOf( + Text("the enum constant of this enum type with the specified name") + ))), + Throws( + name = "java.lang.IllegalArgumentException", + exceptionAddress = DRI( + packageName = "java.lang", + classNames = "IllegalArgumentException", + target = PointingToDeclaration + ), + root = P(listOf( + Text("if this enum type has no constant with the specified name") + )) + ), + Throws( + name = "java.lang.NullPointerException", + exceptionAddress = DRI( + packageName = "java.lang", + classNames = "NullPointerException", + target = PointingToDeclaration + ), + root = P(listOf( + Text("if name is null") + )) + ), + Since(root = P(listOf( + Text("1.5") + ))) +)) + +/** + * Adaptation of text from the Java SE specification. + * + * See https://docs.oracle.com/javase/specs/jls/se18/html/jls-8.html#jls-8.9.3 + */ +internal val JAVA_ENUM_VALUES_DOCUMENTATION = DocumentationNode(listOf( + Description( + P(listOf( + Text( + "Returns an array containing the enum constants of this type, " + + "in the same order as they appear in the body of the declaration" + ) + )) + ) +)) + + +internal fun PsiMethod.isDocumentedSyntheticMethod() = this.isSyntheticEnumValuesMethod() || this.isSyntheticEnumValueOfMethod() + +internal fun PsiMethod.getSyntheticMethodDocumentation(): DocumentationNode? { + return when { + this.isSyntheticEnumValuesMethod() -> JAVA_ENUM_VALUES_DOCUMENTATION + this.isSyntheticEnumValueOfMethod() -> JAVA_ENUM_VALUE_OF_DOCUMENTATION + else -> null + } +} + +private fun PsiMethod.isSyntheticEnumValuesMethod() = this.isSyntheticEnumFunction() && this.name == "values" +private fun PsiMethod.isSyntheticEnumValueOfMethod() = this.isSyntheticEnumFunction() && this.name == "valueOf" +private fun PsiMethod.isSyntheticEnumFunction() = this is SyntheticElement && this.containingClass?.isEnum == true + diff --git a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt index 5c57c4031b..cedf8b6326 100644 --- a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt +++ b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt @@ -2,10 +2,11 @@ package translators import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.translators.descriptors.KOTLIN_ENUM_VALUES_DOCUMENTATION +import org.jetbrains.dokka.base.translators.descriptors.KOTLIN_ENUM_VALUE_OF_DOCUMENTATION import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.doc.* -import org.junit.Assert import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Disabled @@ -853,6 +854,63 @@ class DefaultDescriptorToDocumentableTranslatorTest : BaseAbstractTest() { } } } + + @Test + fun `should have documentation for synthetic enum functions`() { + testInline( + """ + |/src/main/kotlin/test/KotlinEnum.kt + |package test + | + |enum class KotlinEnum { + | FOO, BAR; + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + val kotlinEnum = module.packages.find { it.name == "test" } + ?.classlikes + ?.single { it.name == "KotlinEnum" } + checkNotNull(kotlinEnum) + + val valuesFunction = kotlinEnum.functions.single { it.name == "values" } + val expectedValuesType = GenericTypeConstructor( + dri = DRI( + packageName = "kotlin", + classNames = "Array" + ), + projections = listOf( + Invariance( + GenericTypeConstructor( + dri = DRI( + packageName = "test", + classNames = "KotlinEnum" + ), + projections = emptyList() + ) + ) + ) + ) + assertEquals(KOTLIN_ENUM_VALUES_DOCUMENTATION, valuesFunction.documentation.values.single()) + assertEquals(expectedValuesType, valuesFunction.type) + + val valueOfFunction = kotlinEnum.functions.single { it.name == "valueOf" } + val expectedValueOfType = GenericTypeConstructor( + dri = DRI( + packageName = "test", + classNames = "KotlinEnum" + ), + projections = emptyList() + ) + assertEquals(KOTLIN_ENUM_VALUE_OF_DOCUMENTATION, valueOfFunction.documentation.values.single()) + assertEquals(expectedValueOfType, valueOfFunction.type) + + val valueOfParamDRI = (valueOfFunction.parameters.single().type as GenericTypeConstructor).dri + assertEquals(DRI(packageName = "kotlin", classNames = "String"), valueOfParamDRI) + } + } + } } private sealed class TestSuite { diff --git a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt index 5f42bd9abe..27ea794ef1 100644 --- a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt +++ b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt @@ -3,6 +3,8 @@ package translators import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.translators.psi.JAVA_ENUM_VALUES_DOCUMENTATION +import org.jetbrains.dokka.base.translators.psi.JAVA_ENUM_VALUE_OF_DOCUMENTATION import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.PointingToDeclaration import org.jetbrains.dokka.model.* @@ -591,4 +593,59 @@ class DefaultPsiToDocumentableTranslatorTest : BaseAbstractTest() { } } } + + @Test + fun `should have documentation for synthetic enum functions`() { + testInline( + """ + |/src/main/java/test/JavaEnum.java + |package test + | + |public enum JavaEnum { + | FOO, BAR; + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + val kotlinEnum = module.packages.find { it.name == "test" } + ?.classlikes + ?.single { it.name == "JavaEnum" } + checkNotNull(kotlinEnum) + + val valuesFunction = kotlinEnum.functions.single { it.name == "values" } + val expectedValuesType = GenericTypeConstructor( + dri = DRI( + packageName = "kotlin", + classNames = "Array" + ), + projections = listOf( + GenericTypeConstructor( + dri = DRI( + packageName = "test", + classNames = "JavaEnum" + ), + projections = emptyList() + ) + ) + ) + assertEquals(JAVA_ENUM_VALUES_DOCUMENTATION, valuesFunction.documentation.values.single()) + assertEquals(expectedValuesType, valuesFunction.type) + + val valueOfFunction = kotlinEnum.functions.single { it.name == "valueOf" } + val expectedValueOfType = GenericTypeConstructor( + dri = DRI( + packageName = "test", + classNames = "JavaEnum" + ), + projections = emptyList() + ) + assertEquals(JAVA_ENUM_VALUE_OF_DOCUMENTATION, valueOfFunction.documentation.values.single()) + assertEquals(expectedValueOfType, valueOfFunction.type) + + val valueOfParamDRI = (valueOfFunction.parameters.single().type as GenericTypeConstructor).dri + assertEquals(DRI(packageName = "java.lang", classNames = "String"), valueOfParamDRI) + } + } + } } From 5eec409681d7d48d92ae4ce948dcfe701350cfb4 Mon Sep 17 00:00:00 2001 From: IgnatBeresnev Date: Thu, 1 Sep 2022 01:28:17 +0200 Subject: [PATCH 2/8] Fix tests --- .../src/test/kotlin/enums/JavaEnumsTest.kt | 46 ------------------- .../src/test/kotlin/enums/KotlinEnumsTest.kt | 2 +- 2 files changed, 1 insertion(+), 47 deletions(-) diff --git a/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt b/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt index 20805a7c5c..e8b9e92b00 100644 --- a/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt +++ b/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt @@ -68,50 +68,4 @@ class JavaEnumsTest : BaseAbstractTest() { } } } - - @Test - fun `should mark synthetic functions generated for Kotlin as obvious`() { - val writerPlugin = TestOutputWriterPlugin() - testInline( - """ - |/src/main/java/basic/JavaEnum.java - |package testpackage - | - |public enum JavaEnum { - | ONE, TWO - |} - """.trimMargin(), - basicConfiguration, - pluginOverrides = listOf(writerPlugin) - ) { - documentablesCreationStage = { modules -> - val pckg = modules.flatMap { it.packages }.single { it.packageName == "testpackage" } - val enum = pckg.children.single { it is DEnum } as DEnum - - // there's two with the same name, one inherited from - // java.lang.Enum and one is synthetic for Kotlin interop - enum.functions.filter { it.name == "valueOf" }.let { valueOfMethods -> - assertEquals(2, valueOfMethods.size) - - val valueOfFromKotlin = valueOfMethods[0] - assertEquals( - "testpackage/JavaEnum/valueOf/#java.lang.String/PointingToDeclaration/", - valueOfFromKotlin.dri.toString() - ) - assertNotNull(valueOfFromKotlin.extra[ObviousMember]) - - val valueOfFromJava = valueOfMethods[1] - assertEquals( - "java.lang/Enum/valueOf/#java.lang.Class#java.lang.String/PointingToDeclaration/", - valueOfFromJava.dri.toString() - ) - assertNotNull(valueOfFromJava.extra[ObviousMember]) - } - - val valuesMethod = enum.functions.single { it.name == "values" } - assertEquals("testpackage/JavaEnum/values/#/PointingToDeclaration/", valuesMethod.dri.toString()) - assertNotNull(valuesMethod.extra[ObviousMember]) - } - } - } } diff --git a/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt b/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt index 1fd33f6f71..655a8b827c 100644 --- a/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt +++ b/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt @@ -116,7 +116,7 @@ class KotlinEnumsTest : BaseAbstractTest() { val testEnumNode = packagePage.children[0] assertEquals("TestEnum", testEnumNode.name) - val enumEntries = testEnumNode.children + val enumEntries = testEnumNode.children.filterIsInstance() assertEquals(10, enumEntries.size) assertEquals("ZERO", enumEntries[0].name) From 63c8d641e4c61a4dcda459166825130fcabc1806 Mon Sep 17 00:00:00 2001 From: IgnatBeresnev Date: Thu, 8 Sep 2022 02:45:04 +0200 Subject: [PATCH 3/8] Extract hardcoded Enum documentation into text template files --- .../kotlin/StdlibGradleIntegrationTest.kt | 32 +++++ ...faultDescriptorToDocumentableTranslator.kt | 5 +- ...thesizedDescriptorDocumentationProvider.kt | 62 ---------- ...yntheticDescriptorDocumentationProvider.kt | 47 ++++++++ .../psi/DefaultPsiToDocumentableTranslator.kt | 8 +- .../SynheticElementDocumentationProvider.kt | 110 +++++------------- .../translators/psi/parsers/JavadocParser.kt | 5 +- .../docs/javadoc/EnumValueOf.java.template | 12 ++ .../docs/javadoc/EnumValues.java.template | 8 ++ .../dokka/docs/kdoc/EnumValueOf.kt.template | 4 + .../dokka/docs/kdoc/EnumValues.kt.template | 3 + ...tDescriptorToDocumentableTranslatorTest.kt | 89 ++++++++++++-- .../DefaultPsiToDocumentableTranslatorTest.kt | 105 ++++++++++++++++- 13 files changed, 327 insertions(+), 163 deletions(-) delete mode 100644 plugins/base/src/main/kotlin/translators/descriptors/SynthesizedDescriptorDocumentationProvider.kt create mode 100644 plugins/base/src/main/kotlin/translators/descriptors/SyntheticDescriptorDocumentationProvider.kt create mode 100644 plugins/base/src/main/resources/dokka/docs/javadoc/EnumValueOf.java.template create mode 100644 plugins/base/src/main/resources/dokka/docs/javadoc/EnumValues.java.template create mode 100644 plugins/base/src/main/resources/dokka/docs/kdoc/EnumValueOf.kt.template create mode 100644 plugins/base/src/main/resources/dokka/docs/kdoc/EnumValues.kt.template diff --git a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/StdlibGradleIntegrationTest.kt b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/StdlibGradleIntegrationTest.kt index ba51a1ceb8..6fb1110027 100644 --- a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/StdlibGradleIntegrationTest.kt +++ b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/StdlibGradleIntegrationTest.kt @@ -7,6 +7,7 @@ import org.jetbrains.dokka.it.gradle.AbstractGradleIntegrationTest import org.jetbrains.dokka.it.gradle.BuildVersions import org.junit.runners.Parameterized import java.io.File +import java.net.URL import kotlin.test.* class StdlibGradleIntegrationTest(override val versions: BuildVersions) : AbstractGradleIntegrationTest(), @@ -59,4 +60,35 @@ class StdlibGradleIntegrationTest(override val versions: BuildVersions) : Abstra assertNoUnsubstitutedTemplatesInHtml(file) } } + + /** + * Documentation for Enum's synthetic values() and valueOf() functions is only present in source code, + * but not present in the descriptors. However, Dokka needs to generate documentation for these functions, + * so it ships with hardcoded kdoc templates. + * + * This test exists to make sure documentation for these hardcoded synthetic functions does not change, + * and fails if it does, indicating that it needs to be updated. + */ + @Test + fun shouldAssertEnumDocumentationHasNotChanged() { + val sourcesLink = "https://raw.githubusercontent.com/JetBrains/kotlin/master/core/builtins/native/kotlin/Enum.kt" + val sources = URL(sourcesLink).readText() + + val expectedValuesDoc = + " /**\n" + + " * Returns an array containing the constants of this enum type, in the order they're declared.\n" + + " * This method may be used to iterate over the constants.\n" + + " * @values\n" + + " */" + check(sources.contains(expectedValuesDoc)) + + val expectedValueOfDoc = + " /**\n" + + " * Returns the enum constant of this type with the specified name. The string must match exactly " + + "an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)\n" + + " * @throws IllegalArgumentException if this enum type has no constant with the specified name\n" + + " * @valueOf\n" + + " */" + check(sources.contains(expectedValueOfDoc)) + } } diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt index 607218dc2e..e3e1a33ea1 100644 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt @@ -136,6 +136,7 @@ private class DokkaDescriptorVisitor( private val logger: DokkaLogger ) { private val javadocParser = JavadocParser(logger, resolutionFacade) + private val syntheticDocProvider = SyntheticDescriptorDocumentationProvider(resolutionFacade) private fun Collection.filterDescriptorsInSourceSet() = filter { it.toSourceElement.containingFile.toString().let { path -> @@ -596,7 +597,7 @@ private class DokkaDescriptorVisitor( private fun FunctionDescriptor.getDocumentation(): SourceSetDependent { val isSynthesized = this.kind == CallableMemberDescriptor.Kind.SYNTHESIZED return if (isSynthesized) { - this.getSyntheticFunctionDocumentation()?.toSourceSetDependent() ?: emptyMap() + syntheticDocProvider.getDocumentation(this)?.toSourceSetDependent() ?: emptyMap() } else { this.resolveDescriptorData() } @@ -617,7 +618,7 @@ private class DokkaDescriptorVisitor( private fun FunctionDescriptor.isObvious(): Boolean { return kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE - || (kind == CallableMemberDescriptor.Kind.SYNTHESIZED && !this.isDocumentedSyntheticFunction()) + || (kind == CallableMemberDescriptor.Kind.SYNTHESIZED && !syntheticDocProvider.isDocumented(this)) || containingDeclaration.fqNameOrNull()?.isObvious() == true } diff --git a/plugins/base/src/main/kotlin/translators/descriptors/SynthesizedDescriptorDocumentationProvider.kt b/plugins/base/src/main/kotlin/translators/descriptors/SynthesizedDescriptorDocumentationProvider.kt deleted file mode 100644 index 1395242b0a..0000000000 --- a/plugins/base/src/main/kotlin/translators/descriptors/SynthesizedDescriptorDocumentationProvider.kt +++ /dev/null @@ -1,62 +0,0 @@ -package org.jetbrains.dokka.base.translators.descriptors - -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.links.PointingToDeclaration -import org.jetbrains.dokka.model.doc.* -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.resolve.DescriptorFactory - -/** - * Copy-pasted from kotlin stdlib sources, it should be present in Enum.kt file - * - * See https://github.com/JetBrains/kotlin/blob/master/core/builtins/native/kotlin/Enum.kt - */ -internal val KOTLIN_ENUM_VALUE_OF_DOCUMENTATION = DocumentationNode(listOf( - Description( - P(listOf( - Text( - "Returns the enum constant of this type with the specified name. " + - "The string must match exactly an identifier used to declare an enum " + - "constant in this type. (Extraneous whitespace characters are not permitted.)" - ) - )) - ), - Throws( - name = "kotlin.IllegalArgumentException", - exceptionAddress = DRI( - packageName = "kotlin", - classNames = "IllegalArgumentException", - target = PointingToDeclaration - ), - root = P(listOf( - Text("if this enum type has no constant with the specified name") - )) - ) -)) - -/** - * Copy-pasted from kotlin stdlib sources, it should be present in Enum.kt file - * - * See https://github.com/JetBrains/kotlin/blob/master/core/builtins/native/kotlin/Enum.kt - */ -internal val KOTLIN_ENUM_VALUES_DOCUMENTATION = DocumentationNode(listOf( - Description( - P(listOf( - Text( - "Returns an array containing the constants of this enum type, in the order " + - "they're declared. This method may be used to iterate over the constants." - ) - )) - ) -)) - -internal fun FunctionDescriptor.isDocumentedSyntheticFunction() = - DescriptorFactory.isEnumValuesMethod(this) || DescriptorFactory.isEnumValueOfMethod(this) - -internal fun FunctionDescriptor.getSyntheticFunctionDocumentation(): DocumentationNode? { - return when { - DescriptorFactory.isEnumValuesMethod(this) -> KOTLIN_ENUM_VALUES_DOCUMENTATION - DescriptorFactory.isEnumValueOfMethod(this) -> KOTLIN_ENUM_VALUE_OF_DOCUMENTATION - else -> null - } -} diff --git a/plugins/base/src/main/kotlin/translators/descriptors/SyntheticDescriptorDocumentationProvider.kt b/plugins/base/src/main/kotlin/translators/descriptors/SyntheticDescriptorDocumentationProvider.kt new file mode 100644 index 0000000000..c96b888a94 --- /dev/null +++ b/plugins/base/src/main/kotlin/translators/descriptors/SyntheticDescriptorDocumentationProvider.kt @@ -0,0 +1,47 @@ +package org.jetbrains.dokka.base.translators.descriptors + +import org.jetbrains.dokka.analysis.DokkaResolutionFacade +import org.jetbrains.dokka.analysis.from +import org.jetbrains.dokka.base.parsers.MarkdownParser +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink +import org.jetbrains.kotlin.resolve.DescriptorFactory + +private const val ENUM_VALUEOF_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumValueOf.kt.template" +private const val ENUM_VALUES_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumValues.kt.template" + + internal class SyntheticDescriptorDocumentationProvider( + private val resolutionFacade: DokkaResolutionFacade +) { + fun isDocumented(descriptor: DeclarationDescriptor): Boolean = descriptor is FunctionDescriptor + && (DescriptorFactory.isEnumValuesMethod(descriptor) || DescriptorFactory.isEnumValueOfMethod(descriptor)) + + fun getDocumentation(descriptor: DeclarationDescriptor): DocumentationNode? { + val function = descriptor as? FunctionDescriptor ?: return null + return when { + DescriptorFactory.isEnumValuesMethod(function) -> loadTemplate(descriptor, ENUM_VALUES_TEMPLATE_PATH) + DescriptorFactory.isEnumValueOfMethod(function) -> loadTemplate(descriptor, ENUM_VALUEOF_TEMPLATE_PATH) + else -> null + } + } + + private fun loadTemplate(descriptor: DeclarationDescriptor, filePath: String): DocumentationNode? { + val kdoc = loadContent(filePath) ?: return null + val parser = MarkdownParser({ link -> resolveLink(descriptor, link)}, filePath) + return parser.parse(kdoc) + } + + private fun loadContent(filePath: String): String? = javaClass.getResource(filePath)?.readText() + + private fun resolveLink(descriptor: DeclarationDescriptor, link: String): DRI? = + resolveKDocLink( + resolutionFacade.resolveSession.bindingContext, + resolutionFacade, + descriptor, + null, + link.split('.') + ).firstOrNull()?.let { DRI.from(it) } +} diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index 988098cd7b..574fb2e88a 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -97,8 +97,8 @@ class DefaultPsiToDocumentableTranslator( facade: DokkaResolutionFacade, private val logger: DokkaLogger ) { - - private val javadocParser: JavaDocumentationParser = JavadocParser(logger, facade) + private val javadocParser = JavadocParser(logger, facade) + private val syntheticDocProvider = SyntheticElementDocumentationProvider(javadocParser, facade) private val cachedBounds = hashMapOf() @@ -451,11 +451,11 @@ class DefaultPsiToDocumentableTranslator( } private fun PsiMethod.getDocumentation(): DocumentationNode = - this.takeIf { it is SyntheticElement }?.getSyntheticMethodDocumentation() + this.takeIf { it is SyntheticElement }?.let { syntheticDocProvider.getDocumentation(it) } ?: javadocParser.parseDocumentation(this) private fun PsiMethod.isObvious(inheritedFrom: DRI? = null): Boolean { - return (this is SyntheticElement && !this.isDocumentedSyntheticMethod()) + return (this is SyntheticElement && !syntheticDocProvider.isDocumented(this)) || inheritedFrom?.isObvious() == true } diff --git a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt index f50a046c53..6e38dcfb15 100644 --- a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt +++ b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt @@ -1,85 +1,37 @@ package org.jetbrains.dokka.base.translators.psi -import com.intellij.psi.PsiMethod -import com.intellij.psi.SyntheticElement -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.links.PointingToDeclaration -import org.jetbrains.dokka.model.doc.* - -/** - * Adaptation of text taken from [java.lang.Enum.valueOf] - */ -internal val JAVA_ENUM_VALUE_OF_DOCUMENTATION = DocumentationNode(listOf( - Description( - P(listOf( - Text( - "Returns the enum constant of the this enum type with the " + - "specified name. The name must match exactly an identifier used " + - "to declare an enum constant in this type. (Extraneous whitespace " + - "characters are not permitted.)" - ) - )) - ), - Param( - name = "name", - root = P(listOf( - Text("the name of the constant to return") - )) - ), - Return(root = P(listOf( - Text("the enum constant of this enum type with the specified name") - ))), - Throws( - name = "java.lang.IllegalArgumentException", - exceptionAddress = DRI( - packageName = "java.lang", - classNames = "IllegalArgumentException", - target = PointingToDeclaration - ), - root = P(listOf( - Text("if this enum type has no constant with the specified name") - )) - ), - Throws( - name = "java.lang.NullPointerException", - exceptionAddress = DRI( - packageName = "java.lang", - classNames = "NullPointerException", - target = PointingToDeclaration - ), - root = P(listOf( - Text("if name is null") - )) - ), - Since(root = P(listOf( - Text("1.5") - ))) -)) - -/** - * Adaptation of text from the Java SE specification. - * - * See https://docs.oracle.com/javase/specs/jls/se18/html/jls-8.html#jls-8.9.3 - */ -internal val JAVA_ENUM_VALUES_DOCUMENTATION = DocumentationNode(listOf( - Description( - P(listOf( - Text( - "Returns an array containing the enum constants of this type, " + - "in the same order as they appear in the body of the declaration" - ) - )) - ) -)) - - -internal fun PsiMethod.isDocumentedSyntheticMethod() = this.isSyntheticEnumValuesMethod() || this.isSyntheticEnumValueOfMethod() +import com.intellij.psi.* +import com.intellij.psi.javadoc.PsiDocComment +import org.jetbrains.dokka.analysis.DokkaResolutionFacade +import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser +import org.jetbrains.dokka.model.doc.DocumentationNode + +private const val ENUM_VALUEOF_TEMPLATE_PATH = "/dokka/docs/javadoc/EnumValueOf.java.template" +private const val ENUM_VALUES_TEMPLATE_PATH = "/dokka/docs/javadoc/EnumValues.java.template" + +internal class SyntheticElementDocumentationProvider( + private val javadocParser: JavadocParser, + private val resolutionFacade: DokkaResolutionFacade +) { + fun isDocumented(psiElement: PsiElement): Boolean = psiElement is PsiMethod + && (psiElement.isSyntheticEnumValuesMethod() || psiElement.isSyntheticEnumValueOfMethod()) + + fun getDocumentation(psiElement: PsiElement): DocumentationNode? { + val psiMethod = psiElement as? PsiMethod ?: return null + val templatePath = when { + psiMethod.isSyntheticEnumValuesMethod() -> ENUM_VALUES_TEMPLATE_PATH + psiMethod.isSyntheticEnumValueOfMethod() -> ENUM_VALUEOF_TEMPLATE_PATH + else -> return null + } + val docComment = loadSyntheticDoc(psiElement, templatePath) ?: return null + return javadocParser.parseDocComment(docComment, psiElement) + } -internal fun PsiMethod.getSyntheticMethodDocumentation(): DocumentationNode? { - return when { - this.isSyntheticEnumValuesMethod() -> JAVA_ENUM_VALUES_DOCUMENTATION - this.isSyntheticEnumValueOfMethod() -> JAVA_ENUM_VALUE_OF_DOCUMENTATION - else -> null + private fun loadSyntheticDoc(psiElement: PsiElement, path: String): PsiDocComment? { + val containingClassName = (psiElement as? PsiMember)?.containingClass?.name ?: return null + val template = javaClass.getResource(path)?.readText() ?: return null + val text = template.replace("", containingClassName) + return JavaPsiFacade.getElementFactory(resolutionFacade.project).createDocCommentFromText(text) } } diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt index 7dc8e3a0fa..00cc0a3807 100644 --- a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt +++ b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt @@ -50,14 +50,13 @@ class JavadocParser( override fun parseDocumentation(element: PsiNamedElement): DocumentationNode { return when(val comment = findClosestDocComment(element, logger)){ - is JavaDocComment -> parseDocumentation(comment, element) + is JavaDocComment -> parseDocComment(comment.comment, element) is KotlinDocComment -> parseDocumentation(comment) else -> DocumentationNode(emptyList()) } } - private fun parseDocumentation(element: JavaDocComment, context: PsiNamedElement): DocumentationNode { - val docComment = element.comment + internal fun parseDocComment(docComment: PsiDocComment, context: PsiNamedElement): DocumentationNode { val nodes = listOfNotNull(docComment.getDescription()) + docComment.tags.mapNotNull { tag -> parseDocTag(tag, docComment, context) } diff --git a/plugins/base/src/main/resources/dokka/docs/javadoc/EnumValueOf.java.template b/plugins/base/src/main/resources/dokka/docs/javadoc/EnumValueOf.java.template new file mode 100644 index 0000000000..233f881981 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/docs/javadoc/EnumValueOf.java.template @@ -0,0 +1,12 @@ +/** + * Returns the enum constant of this type with the specified + * name. + * The string must match exactly an identifier used to declare + * an enum constant in this type. (Extraneous whitespace + * characters are not permitted.) + * + * @return the enum constant with the specified name + * @throws IllegalArgumentException if this enum type has no + * constant with the specified name + */ + \ No newline at end of file diff --git a/plugins/base/src/main/resources/dokka/docs/javadoc/EnumValues.java.template b/plugins/base/src/main/resources/dokka/docs/javadoc/EnumValues.java.template new file mode 100644 index 0000000000..4aed38a623 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/docs/javadoc/EnumValues.java.template @@ -0,0 +1,8 @@ +/** + * Returns an array containing the constants of this enum + * type, in the order they're declared. This method may be + * used to iterate over the constants. + * + * @return an array containing the constants of this enum + * type, in the order they're declared + */ diff --git a/plugins/base/src/main/resources/dokka/docs/kdoc/EnumValueOf.kt.template b/plugins/base/src/main/resources/dokka/docs/kdoc/EnumValueOf.kt.template new file mode 100644 index 0000000000..fbf8fa8d7c --- /dev/null +++ b/plugins/base/src/main/resources/dokka/docs/kdoc/EnumValueOf.kt.template @@ -0,0 +1,4 @@ +Returns the enum constant of this type with the specified name. The string must match exactly an identifier used +to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.) + +@throws kotlin.IllegalArgumentException if this enum type has no constant with the specified name diff --git a/plugins/base/src/main/resources/dokka/docs/kdoc/EnumValues.kt.template b/plugins/base/src/main/resources/dokka/docs/kdoc/EnumValues.kt.template new file mode 100644 index 0000000000..c0e3559cb2 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/docs/kdoc/EnumValues.kt.template @@ -0,0 +1,3 @@ +Returns an array containing the constants of this enum type, in the order they're declared. + +This method may be used to iterate over the constants. diff --git a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt index cedf8b6326..47b457218e 100644 --- a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt +++ b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt @@ -2,9 +2,8 @@ package translators import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest -import org.jetbrains.dokka.base.translators.descriptors.KOTLIN_ENUM_VALUES_DOCUMENTATION -import org.jetbrains.dokka.base.translators.descriptors.KOTLIN_ENUM_VALUE_OF_DOCUMENTATION import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.PointingToDeclaration import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.doc.* import org.junit.jupiter.api.Assertions.assertEquals @@ -21,6 +20,7 @@ class DefaultDescriptorToDocumentableTranslatorTest : BaseAbstractTest() { sourceSets { sourceSet { sourceRoots = listOf("src/main/kotlin") + classpath = listOf(commonStdlibPath!!, jvmStdlibPath!!) } } } @@ -856,7 +856,7 @@ class DefaultDescriptorToDocumentableTranslatorTest : BaseAbstractTest() { } @Test - fun `should have documentation for synthetic enum functions`() { + fun `should have documentation for synthetic Enum values functions`() { testInline( """ |/src/main/kotlin/test/KotlinEnum.kt @@ -873,8 +873,8 @@ class DefaultDescriptorToDocumentableTranslatorTest : BaseAbstractTest() { ?.classlikes ?.single { it.name == "KotlinEnum" } checkNotNull(kotlinEnum) - val valuesFunction = kotlinEnum.functions.single { it.name == "values" } + val expectedValuesType = GenericTypeConstructor( dri = DRI( packageName = "kotlin", @@ -892,10 +892,50 @@ class DefaultDescriptorToDocumentableTranslatorTest : BaseAbstractTest() { ) ) ) - assertEquals(KOTLIN_ENUM_VALUES_DOCUMENTATION, valuesFunction.documentation.values.single()) assertEquals(expectedValuesType, valuesFunction.type) - val valueOfFunction = kotlinEnum.functions.single { it.name == "valueOf" } + val expectedDocumentation = DocumentationNode(listOf( + Description( + CustomDocTag( + children = listOf( + P(listOf( + Text( + "Returns an array containing the constants of this enum type, in the order " + + "they're declared." + ), + )), + P(listOf( + Text("This method may be used to iterate over the constants.") + )) + ), + name = "MARKDOWN_FILE" + ) + ) + )) + assertEquals(expectedDocumentation, valuesFunction.documentation.values.single()) + } + } + } + + @Test + fun `should have documentation for synthetic Enum valueOf functions`() { + testInline( + """ + |/src/main/kotlin/test/KotlinEnum.kt + |package test + | + |enum class KotlinEnum { + | FOO, BAR; + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + val kotlinEnum = module.packages.find { it.name == "test" } + ?.classlikes + ?.single { it.name == "KotlinEnum" } + checkNotNull(kotlinEnum) + val expectedValueOfType = GenericTypeConstructor( dri = DRI( packageName = "test", @@ -903,7 +943,42 @@ class DefaultDescriptorToDocumentableTranslatorTest : BaseAbstractTest() { ), projections = emptyList() ) - assertEquals(KOTLIN_ENUM_VALUE_OF_DOCUMENTATION, valueOfFunction.documentation.values.single()) + + val expectedDocumentation = DocumentationNode(listOf( + Description( + CustomDocTag( + children = listOf( + P(listOf( + Text( + "Returns the enum constant of this type with the specified name. " + + "The string must match exactly an identifier used to declare an enum " + + "constant in this type. (Extraneous whitespace characters are not permitted.)" + ) + )) + ), + name = "MARKDOWN_FILE" + ) + ), + Throws( + root = CustomDocTag( + children = listOf( + P(listOf( + Text("if this enum type has no constant with the specified name") + )) + ), + name = "MARKDOWN_FILE" + ), + name = "kotlin.IllegalArgumentException", + exceptionAddress = DRI( + packageName = "kotlin", + classNames = "IllegalArgumentException", + target = PointingToDeclaration + ), + ) + )) + + val valueOfFunction = kotlinEnum.functions.single { it.name == "valueOf" } + assertEquals(expectedDocumentation, valueOfFunction.documentation.values.single()) assertEquals(expectedValueOfType, valueOfFunction.type) val valueOfParamDRI = (valueOfFunction.parameters.single().type as GenericTypeConstructor).dri diff --git a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt index 27ea794ef1..711b9c02cc 100644 --- a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt +++ b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt @@ -3,12 +3,10 @@ package translators import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest -import org.jetbrains.dokka.base.translators.psi.JAVA_ENUM_VALUES_DOCUMENTATION -import org.jetbrains.dokka.base.translators.psi.JAVA_ENUM_VALUE_OF_DOCUMENTATION import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.PointingToDeclaration import org.jetbrains.dokka.model.* -import org.jetbrains.dokka.model.doc.Text +import org.jetbrains.dokka.model.doc.* import org.jetbrains.dokka.plugability.DokkaPlugin import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.* @@ -595,7 +593,7 @@ class DefaultPsiToDocumentableTranslatorTest : BaseAbstractTest() { } @Test - fun `should have documentation for synthetic enum functions`() { + fun `should have documentation for synthetic Enum values functions`() { testInline( """ |/src/main/java/test/JavaEnum.java @@ -614,6 +612,35 @@ class DefaultPsiToDocumentableTranslatorTest : BaseAbstractTest() { checkNotNull(kotlinEnum) val valuesFunction = kotlinEnum.functions.single { it.name == "values" } + + val expectedDocumentation = DocumentationNode(listOf( + Description( + CustomDocTag( + children = listOf( + P(listOf( + Text( + "Returns an array containing the constants of this enum type, " + + "in the order they're declared. This method may be used to " + + "iterate over the constants." + ), + )) + ), + name = "MARKDOWN_FILE" + ) + ), + Return( + CustomDocTag( + children = listOf( + P(listOf( + Text("an array containing the constants of this enum type, in the order they're declared") + )) + ), + name = "MARKDOWN_FILE" + ) + ) + )) + assertEquals(expectedDocumentation, valuesFunction.documentation.values.single()) + val expectedValuesType = GenericTypeConstructor( dri = DRI( packageName = "kotlin", @@ -629,10 +656,77 @@ class DefaultPsiToDocumentableTranslatorTest : BaseAbstractTest() { ) ) ) - assertEquals(JAVA_ENUM_VALUES_DOCUMENTATION, valuesFunction.documentation.values.single()) assertEquals(expectedValuesType, valuesFunction.type) + } + } + } + + @Test + fun `should have documentation for synthetic Enum valueOf functions`() { + testInline( + """ + |/src/main/java/test/JavaEnum.java + |package test + | + |public enum JavaEnum { + | FOO, BAR; + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + val kotlinEnum = module.packages.find { it.name == "test" } + ?.classlikes + ?.single { it.name == "JavaEnum" } + checkNotNull(kotlinEnum) val valueOfFunction = kotlinEnum.functions.single { it.name == "valueOf" } + + val expectedDocumentation = DocumentationNode(listOf( + Description( + CustomDocTag( + children = listOf( + P(listOf( + Text( + "Returns the enum constant of this type with the " + + "specified name. The string must match exactly an identifier used " + + "to declare an enum constant in this type. (Extraneous whitespace " + + "characters are not permitted.)" + ) + )) + ), + name = "MARKDOWN_FILE" + ) + ), + Return( + root = CustomDocTag( + children = listOf( + P(listOf( + Text("the enum constant with the specified name") + )) + ), + name = "MARKDOWN_FILE" + ) + ), + Throws( + name = "java.lang.IllegalArgumentException", + exceptionAddress = DRI( + packageName = "java.lang", + classNames = "IllegalArgumentException", + target = PointingToDeclaration + ), + root = CustomDocTag( + children = listOf( + P(listOf( + Text("if this enum type has no constant with the specified name") + )) + ), + name = "MARKDOWN_FILE" + ) + ), + )) + assertEquals(expectedDocumentation, valueOfFunction.documentation.values.single()) + val expectedValueOfType = GenericTypeConstructor( dri = DRI( packageName = "test", @@ -640,7 +734,6 @@ class DefaultPsiToDocumentableTranslatorTest : BaseAbstractTest() { ), projections = emptyList() ) - assertEquals(JAVA_ENUM_VALUE_OF_DOCUMENTATION, valueOfFunction.documentation.values.single()) assertEquals(expectedValueOfType, valueOfFunction.type) val valueOfParamDRI = (valueOfFunction.parameters.single().type as GenericTypeConstructor).dri From d744a93bb6d0c6d80d1a2d8bd5254e0e058ae9d4 Mon Sep 17 00:00:00 2001 From: IgnatBeresnev Date: Thu, 8 Sep 2022 06:56:15 +0200 Subject: [PATCH 4/8] Move Enum doc test out of gradle integration tests --- .../it/StdLibDocumentationIntegrationTest.kt | 38 +++++++++++++++++++ .../kotlin/StdlibGradleIntegrationTest.kt | 31 --------------- 2 files changed, 38 insertions(+), 31 deletions(-) create mode 100644 integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt diff --git a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt new file mode 100644 index 0000000000..be683a4d8b --- /dev/null +++ b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt @@ -0,0 +1,38 @@ +package org.jetbrains.dokka.it + +import java.net.URL +import kotlin.test.Test + +class StdLibDocumentationIntegrationTest { + + /** + * Documentation for Enum's synthetic values() and valueOf() functions is only present in source code, + * but not present in the descriptors. However, Dokka needs to generate documentation for these functions, + * so it ships with hardcoded kdoc templates. + * + * This test exists to make sure documentation for these hardcoded synthetic functions does not change, + * and fails if it does, indicating that it needs to be updated. + */ + @Test + fun shouldAssertEnumDocumentationHasNotChanged() { + val sourcesLink = "https://raw.githubusercontent.com/JetBrains/kotlin/master/core/builtins/native/kotlin/Enum.kt" + val sources = URL(sourcesLink).readText() + + val expectedValuesDoc = + " /**\n" + + " * Returns an array containing the constants of this enum type, in the order they're declared.\n" + + " * This method may be used to iterate over the constants.\n" + + " * @values\n" + + " */" + check(sources.contains(expectedValuesDoc)) + + val expectedValueOfDoc = + " /**\n" + + " * Returns the enum constant of this type with the specified name. The string must match exactly " + + "an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)\n" + + " * @throws IllegalArgumentException if this enum type has no constant with the specified name\n" + + " * @valueOf\n" + + " */" + check(sources.contains(expectedValueOfDoc)) + } +} diff --git a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/StdlibGradleIntegrationTest.kt b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/StdlibGradleIntegrationTest.kt index 6fb1110027..eed1a7f451 100644 --- a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/StdlibGradleIntegrationTest.kt +++ b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/StdlibGradleIntegrationTest.kt @@ -60,35 +60,4 @@ class StdlibGradleIntegrationTest(override val versions: BuildVersions) : Abstra assertNoUnsubstitutedTemplatesInHtml(file) } } - - /** - * Documentation for Enum's synthetic values() and valueOf() functions is only present in source code, - * but not present in the descriptors. However, Dokka needs to generate documentation for these functions, - * so it ships with hardcoded kdoc templates. - * - * This test exists to make sure documentation for these hardcoded synthetic functions does not change, - * and fails if it does, indicating that it needs to be updated. - */ - @Test - fun shouldAssertEnumDocumentationHasNotChanged() { - val sourcesLink = "https://raw.githubusercontent.com/JetBrains/kotlin/master/core/builtins/native/kotlin/Enum.kt" - val sources = URL(sourcesLink).readText() - - val expectedValuesDoc = - " /**\n" + - " * Returns an array containing the constants of this enum type, in the order they're declared.\n" + - " * This method may be used to iterate over the constants.\n" + - " * @values\n" + - " */" - check(sources.contains(expectedValuesDoc)) - - val expectedValueOfDoc = - " /**\n" + - " * Returns the enum constant of this type with the specified name. The string must match exactly " + - "an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)\n" + - " * @throws IllegalArgumentException if this enum type has no constant with the specified name\n" + - " * @valueOf\n" + - " */" - check(sources.contains(expectedValueOfDoc)) - } } From bb17d690a164140d96e1b472c37f9cbde343758a Mon Sep 17 00:00:00 2001 From: IgnatBeresnev Date: Wed, 14 Sep 2022 17:20:57 +0200 Subject: [PATCH 5/8] Remove unused tag --- .../translators/psi/SynheticElementDocumentationProvider.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt index 6e38dcfb15..b945d44a01 100644 --- a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt +++ b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt @@ -29,8 +29,7 @@ internal class SyntheticElementDocumentationProvider( private fun loadSyntheticDoc(psiElement: PsiElement, path: String): PsiDocComment? { val containingClassName = (psiElement as? PsiMember)?.containingClass?.name ?: return null - val template = javaClass.getResource(path)?.readText() ?: return null - val text = template.replace("", containingClassName) + val text = javaClass.getResource(path)?.readText() ?: return null return JavaPsiFacade.getElementFactory(resolutionFacade.project).createDocCommentFromText(text) } } From 027ec1c79ebc9549abad0751a5f144a7249f0383 Mon Sep 17 00:00:00 2001 From: IgnatBeresnev Date: Wed, 14 Sep 2022 17:38:44 +0200 Subject: [PATCH 6/8] Remove unused variable --- .../translators/psi/SynheticElementDocumentationProvider.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt index b945d44a01..19e363ea78 100644 --- a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt +++ b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt @@ -28,7 +28,6 @@ internal class SyntheticElementDocumentationProvider( } private fun loadSyntheticDoc(psiElement: PsiElement, path: String): PsiDocComment? { - val containingClassName = (psiElement as? PsiMember)?.containingClass?.name ?: return null val text = javaClass.getResource(path)?.readText() ?: return null return JavaPsiFacade.getElementFactory(resolutionFacade.project).createDocCommentFromText(text) } From 9839c70cb5eb0065daa60761486a7b4460aabbc1 Mon Sep 17 00:00:00 2001 From: IgnatBeresnev Date: Wed, 14 Sep 2022 17:43:25 +0200 Subject: [PATCH 7/8] Remove unused parameter --- .../translators/psi/SynheticElementDocumentationProvider.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt index 19e363ea78..376c0940dc 100644 --- a/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt +++ b/plugins/base/src/main/kotlin/translators/psi/SynheticElementDocumentationProvider.kt @@ -23,11 +23,11 @@ internal class SyntheticElementDocumentationProvider( psiMethod.isSyntheticEnumValueOfMethod() -> ENUM_VALUEOF_TEMPLATE_PATH else -> return null } - val docComment = loadSyntheticDoc(psiElement, templatePath) ?: return null + val docComment = loadSyntheticDoc(templatePath) ?: return null return javadocParser.parseDocComment(docComment, psiElement) } - private fun loadSyntheticDoc(psiElement: PsiElement, path: String): PsiDocComment? { + private fun loadSyntheticDoc(path: String): PsiDocComment? { val text = javaClass.getResource(path)?.readText() ?: return null return JavaPsiFacade.getElementFactory(resolutionFacade.project).createDocCommentFromText(text) } From 586137f7e95980efefb452652dda0b4752a0fe0b Mon Sep 17 00:00:00 2001 From: IgnatBeresnev Date: Thu, 15 Sep 2022 02:13:44 +0200 Subject: [PATCH 8/8] Correct indents for better readability --- .../it/StdLibDocumentationIntegrationTest.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt index be683a4d8b..ee6f4ea25d 100644 --- a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt +++ b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt @@ -20,19 +20,19 @@ class StdLibDocumentationIntegrationTest { val expectedValuesDoc = " /**\n" + - " * Returns an array containing the constants of this enum type, in the order they're declared.\n" + - " * This method may be used to iterate over the constants.\n" + - " * @values\n" + - " */" + " * Returns an array containing the constants of this enum type, in the order they're declared.\n" + + " * This method may be used to iterate over the constants.\n" + + " * @values\n" + + " */" check(sources.contains(expectedValuesDoc)) val expectedValueOfDoc = " /**\n" + - " * Returns the enum constant of this type with the specified name. The string must match exactly " + - "an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)\n" + - " * @throws IllegalArgumentException if this enum type has no constant with the specified name\n" + - " * @valueOf\n" + - " */" + " * Returns the enum constant of this type with the specified name. The string must match exactly " + + "an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)\n" + + " * @throws IllegalArgumentException if this enum type has no constant with the specified name\n" + + " * @valueOf\n" + + " */" check(sources.contains(expectedValueOfDoc)) } }