diff options
Diffstat (limited to 'plugins/base')
6 files changed, 384 insertions, 57 deletions
diff --git a/plugins/base/api/base.api b/plugins/base/api/base.api index c299d547..3e74e37b 100644 --- a/plugins/base/api/base.api +++ b/plugins/base/api/base.api @@ -1292,6 +1292,13 @@ public final class org/jetbrains/dokka/base/transformers/pages/annotations/Since public fun invoke (Lorg/jetbrains/dokka/model/DModule;Lorg/jetbrains/dokka/plugability/DokkaContext;)Lorg/jetbrains/dokka/model/DModule; } +public final class org/jetbrains/dokka/base/transformers/pages/annotations/SinceKotlinVersion : java/lang/Comparable { + public fun <init> (Ljava/lang/String;)V + public synthetic fun compareTo (Ljava/lang/Object;)I + public fun compareTo (Lorg/jetbrains/dokka/base/transformers/pages/annotations/SinceKotlinVersion;)I + public fun toString ()Ljava/lang/String; +} + public abstract interface class org/jetbrains/dokka/base/transformers/pages/comments/CommentsToContentConverter { public abstract fun buildContent (Lorg/jetbrains/dokka/model/doc/DocTag;Lorg/jetbrains/dokka/pages/DCI;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;)Ljava/util/List; } diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index 86160b4a..ddc06fe0 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -36,7 +36,6 @@ import org.jetbrains.dokka.base.translators.descriptors.ExternalClasslikesTransl import org.jetbrains.dokka.base.translators.descriptors.ExternalDocumentablesProvider import org.jetbrains.dokka.base.utils.NoopIntellijLoggerFactory import org.jetbrains.dokka.plugability.DokkaPlugin -import org.jetbrains.dokka.plugability.configuration import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer import org.jetbrains.dokka.transformers.pages.PageTransformer @@ -133,7 +132,9 @@ class DokkaBase : DokkaPlugin() { } val sinceKotlinTransformer by extending { - CoreExtensions.documentableTransformer providing ::SinceKotlinTransformer + CoreExtensions.documentableTransformer providing ::SinceKotlinTransformer applyIf { SinceKotlinTransformer.shouldDisplaySinceKotlin() } order { + before(extensionsExtractor) + } } val inheritorsExtractor by extending { @@ -157,7 +158,7 @@ class DokkaBase : DokkaPlugin() { } val sinceKotlinTagContentProvider by extending { - customTagContentProvider with SinceKotlinTagContentProvider + customTagContentProvider with SinceKotlinTagContentProvider applyIf { SinceKotlinTransformer.shouldDisplaySinceKotlin() } } val pageMerger by extending { diff --git a/plugins/base/src/main/kotlin/transformers/documentables/ActualTypealiasAdder.kt b/plugins/base/src/main/kotlin/transformers/documentables/ActualTypealiasAdder.kt index 58c601bc..e895db60 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/ActualTypealiasAdder.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/ActualTypealiasAdder.kt @@ -67,12 +67,32 @@ class ActualTypealiasAdder : DocumentableTransformer { if (element.expectPresentInSet != null) { typealiases[element.dri]?.let { ta -> val merged = element.withNewExtras(element.extra + ActualTypealias(ta.underlyingType)).let { - when(it) { - is DClass -> it.copy(sourceSets = element.sourceSets + ta.sourceSets) - is DEnum -> it.copy(sourceSets = element.sourceSets + ta.sourceSets) - is DInterface -> it.copy(sourceSets = element.sourceSets + ta.sourceSets) - is DObject -> it.copy(sourceSets = element.sourceSets + ta.sourceSets) - is DAnnotation -> it.copy(sourceSets = element.sourceSets + ta.sourceSets) + when (it) { + is DClass -> it.copy( + documentation = element.documentation + ta.documentation, + sourceSets = element.sourceSets + ta.sourceSets + ) + + is DEnum -> it.copy( + documentation = element.documentation + ta.documentation, + sourceSets = element.sourceSets + ta.sourceSets + ) + + is DInterface -> it.copy( + documentation = element.documentation + ta.documentation, + sourceSets = element.sourceSets + ta.sourceSets + ) + + is DObject -> it.copy( + documentation = element.documentation + ta.documentation, + sourceSets = element.sourceSets + ta.sourceSets + ) + + is DAnnotation -> it.copy( + documentation = element.documentation + ta.documentation, + sourceSets = element.sourceSets + ta.sourceSets + ) + else -> throw IllegalStateException("${it::class.qualifiedName} ${it.name} cannot have copy its sourceSets") } } diff --git a/plugins/base/src/main/kotlin/transformers/pages/annotations/SinceKotlinTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/annotations/SinceKotlinTransformer.kt index f437ebe3..d81cca89 100644 --- a/plugins/base/src/main/kotlin/transformers/pages/annotations/SinceKotlinTransformer.kt +++ b/plugins/base/src/main/kotlin/transformers/pages/annotations/SinceKotlinTransformer.kt @@ -1,93 +1,180 @@ package org.jetbrains.dokka.base.transformers.pages.annotations +import com.intellij.util.containers.ComparatorUtil.max import org.intellij.markdown.MarkdownElementTypes -import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.annotations import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.doc.CustomDocTag import org.jetbrains.dokka.model.doc.CustomTagWrapper +import org.jetbrains.dokka.model.doc.DocumentationNode import org.jetbrains.dokka.model.doc.Text -import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer +import org.jetbrains.dokka.utilities.associateWithNotNull import org.jetbrains.kotlin.utils.addToStdlib.safeAs +class SinceKotlinVersion constructor(str: String) : Comparable<SinceKotlinVersion> { + private val parts: List<Int> = str.split(".").map { it.toInt() } + + /** + * Corner case: 1.0 == 1.0.0 + */ + override fun compareTo(other: SinceKotlinVersion): Int { + val i1 = parts.listIterator() + val i2 = other.parts.listIterator() + + while (i1.hasNext() || i2.hasNext()) { + val diff = (if (i1.hasNext()) i1.next() else 0) - (if (i2.hasNext()) i2.next() else 0) + if (diff != 0) return diff + } + + return 0 + } + + override fun toString(): String = parts.joinToString(".") +} + class SinceKotlinTransformer(val context: DokkaContext) : DocumentableTransformer { + private val minSinceKotlinVersionOfPlatform = mapOf( + Platform.common to SinceKotlinVersion("1.2"), + Platform.jvm to SinceKotlinVersion("1.0"), + Platform.js to SinceKotlinVersion("1.1"), + Platform.native to SinceKotlinVersion("1.3") + ) + override fun invoke(original: DModule, context: DokkaContext) = original.transform() as DModule - private fun <T : Documentable> T.transform(): Documentable = - when (this) { + private fun <T : Documentable> T.transform(parent: SourceSetDependent<SinceKotlinVersion>? = null): Documentable { + val versions = calculateVersions(parent) + return when (this) { is DModule -> copy( packages = packages.map { it.transform() as DPackage } ) + is DPackage -> copy( classlikes = classlikes.map { it.transform() as DClasslike }, functions = functions.map { it.transform() as DFunction }, - properties = properties.map { it.transform() as DProperty } + properties = properties.map { it.transform() as DProperty }, + typealiases = typealiases.map { it.transform() as DTypeAlias } ) + is DClass -> copy( - documentation = appendSinceKotlin(), - classlikes = classlikes.map { it.transform() as DClasslike }, - functions = functions.map { it.transform() as DFunction }, - properties = properties.map { it.transform() as DProperty } + documentation = appendSinceKotlin(versions), + classlikes = classlikes.map { it.transform(versions) as DClasslike }, + functions = functions.map { it.transform(versions) as DFunction }, + properties = properties.map { it.transform(versions) as DProperty } ) + is DEnum -> copy( - documentation = appendSinceKotlin(), - classlikes = classlikes.map { it.transform() as DClasslike }, - functions = functions.map { it.transform() as DFunction }, - properties = properties.map { it.transform() as DProperty } + documentation = appendSinceKotlin(versions), + classlikes = classlikes.map { it.transform(versions) as DClasslike }, + functions = functions.map { it.transform(versions) as DFunction }, + properties = properties.map { it.transform(versions) as DProperty } ) + is DInterface -> copy( - documentation = appendSinceKotlin(), - classlikes = classlikes.map { it.transform() as DClasslike }, - functions = functions.map { it.transform() as DFunction }, - properties = properties.map { it.transform() as DProperty } + documentation = appendSinceKotlin(versions), + classlikes = classlikes.map { it.transform(versions) as DClasslike }, + functions = functions.map { it.transform(versions) as DFunction }, + properties = properties.map { it.transform(versions) as DProperty } ) + is DObject -> copy( - documentation = appendSinceKotlin(), - classlikes = classlikes.map { it.transform() as DClasslike }, - functions = functions.map { it.transform() as DFunction }, - properties = properties.map { it.transform() as DProperty } + documentation = appendSinceKotlin(versions), + classlikes = classlikes.map { it.transform(versions) as DClasslike }, + functions = functions.map { it.transform(versions) as DFunction }, + properties = properties.map { it.transform(versions) as DProperty } ) + + is DTypeAlias -> copy( + documentation = appendSinceKotlin(versions) + ) + is DAnnotation -> copy( - documentation = appendSinceKotlin(), - classlikes = classlikes.map { it.transform() as DClasslike }, - functions = functions.map { it.transform() as DFunction }, - properties = properties.map { it.transform() as DProperty } + documentation = appendSinceKotlin(versions), + classlikes = classlikes.map { it.transform(versions) as DClasslike }, + functions = functions.map { it.transform(versions) as DFunction }, + properties = properties.map { it.transform(versions) as DProperty } ) + is DFunction -> copy( - documentation = appendSinceKotlin() + documentation = appendSinceKotlin(versions) ) + is DProperty -> copy( - documentation = appendSinceKotlin() + documentation = appendSinceKotlin(versions) ) + is DParameter -> copy( - documentation = appendSinceKotlin() + documentation = appendSinceKotlin(versions) ) + else -> this.also { context.logger.warn("Unrecognized documentable $this while SinceKotlin transformation") } } + } + + private fun List<Annotations.Annotation>.findSinceKotlinAnnotation(): Annotations.Annotation? = + this.find { it.dri.packageName == "kotlin" && it.dri.classNames == "SinceKotlin" } + + private fun Documentable.getVersion(sourceSet: DokkaConfiguration.DokkaSourceSet): SinceKotlinVersion { + val annotatedVersion = + annotations()[sourceSet] + ?.findSinceKotlinAnnotation() + ?.params?.get("version").safeAs<StringValue>()?.value + ?.let { SinceKotlinVersion(it) } - private fun Documentable.appendSinceKotlin() = + val minSinceKotlin = minSinceKotlinVersionOfPlatform[sourceSet.analysisPlatform] + ?: throw IllegalStateException("No value for platform: ${sourceSet.analysisPlatform}") + + return annotatedVersion?.takeIf { version -> version >= minSinceKotlin } ?: minSinceKotlin + } + + + private fun Documentable.calculateVersions(parent: SourceSetDependent<SinceKotlinVersion>?): SourceSetDependent<SinceKotlinVersion> { + return sourceSets.associateWithNotNull { sourceSet -> + val version = getVersion(sourceSet) + val parentVersion = parent?.get(sourceSet) + if (parentVersion != null) + max(version, parentVersion) + else + version + } + } + + private fun Documentable.appendSinceKotlin(versions: SourceSetDependent<SinceKotlinVersion>) = sourceSets.fold(documentation) { acc, sourceSet -> - safeAs<WithExtraProperties<Documentable>>()?.extra?.get(Annotations)?.directAnnotations?.get(sourceSet)?.find { - it.dri == DRI("kotlin", "SinceKotlin") - }?.params?.get("version").safeAs<StringValue>()?.value?.let { version -> + + val version = versions[sourceSet] + + val sinceKotlinCustomTag = CustomTagWrapper( + CustomDocTag( + listOf( + Text( + version.toString() + ) + ), + name = MarkdownElementTypes.MARKDOWN_FILE.name + ), + "Since Kotlin" + ) + if (acc[sourceSet] == null) + acc + (sourceSet to DocumentationNode(listOf(sinceKotlinCustomTag))) + else acc.mapValues { if (it.key == sourceSet) it.value.copy( it.value.children + listOf( - CustomTagWrapper( - CustomDocTag( - listOf( - Text(version.dropWhile { it == '"' }.dropLastWhile { it == '"' } - ) - ), - name = MarkdownElementTypes.MARKDOWN_FILE.name - ), - "Since Kotlin" - ) + sinceKotlinCustomTag ) ) else it.value } - } ?: acc } + + internal companion object { + internal const val SHOULD_DISPLAY_SINCE_KOTLIN_SYS_PROP = "dokka.shouldDisplaySinceKotlin" + internal fun shouldDisplaySinceKotlin() = + System.getProperty(SHOULD_DISPLAY_SINCE_KOTLIN_SYS_PROP) in listOf("true", "1") + } } diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index ff60170c..9d45f7a3 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -802,6 +802,7 @@ open class DefaultPageCreator( props.forEach { +buildSignature(it) contentForBrief(it) + contentForCustomTagsBrief(it) } } } diff --git a/plugins/base/src/test/kotlin/content/annotations/SinceKotlinTest.kt b/plugins/base/src/test/kotlin/content/annotations/SinceKotlinTest.kt index 84f5b647..d4a17869 100644 --- a/plugins/base/src/test/kotlin/content/annotations/SinceKotlinTest.kt +++ b/plugins/base/src/test/kotlin/content/annotations/SinceKotlinTest.kt @@ -1,15 +1,26 @@ package content.annotations import matchers.content.* -import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.transformers.pages.annotations.SinceKotlinTransformer +import org.jetbrains.dokka.base.transformers.pages.annotations.SinceKotlinVersion +import org.jetbrains.dokka.model.DFunction +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.model.doc.CustomTagWrapper +import org.jetbrains.dokka.model.doc.Text import org.jetbrains.dokka.pages.ContentPage -import org.junit.jupiter.api.Test +import org.junit.jupiter.api.* +import signatures.AbstractRenderingTest import utils.ParamAttributes +import utils.TestOutputWriterPlugin +import utils.assertNotNull import utils.bareSignature +import kotlin.test.assertEquals -class SinceKotlinTest : BaseAbstractTest() { - private val testConfiguration = dokkaConfiguration { +class SinceKotlinTest : AbstractRenderingTest() { + + val testConfiguration = dokkaConfiguration { sourceSets { sourceSet { sourceRoots = listOf("src/") @@ -18,8 +29,208 @@ class SinceKotlinTest : BaseAbstractTest() { } } + @BeforeEach + fun setSystemProperty() { + System.setProperty(SinceKotlinTransformer.SHOULD_DISPLAY_SINCE_KOTLIN_SYS_PROP, "true") + } + @AfterEach + fun clearSystemProperty() { + System.clearProperty(SinceKotlinTransformer.SHOULD_DISPLAY_SINCE_KOTLIN_SYS_PROP) + } + + @Test + fun versionsComparing() { + assert(SinceKotlinVersion("1.0").compareTo(SinceKotlinVersion("1.0")) == 0) + assert(SinceKotlinVersion("1.0.0").compareTo(SinceKotlinVersion("1")) == 0) + assert(SinceKotlinVersion("1.0") >= SinceKotlinVersion("1.0")) + assert(SinceKotlinVersion("1.1") > SinceKotlinVersion("1")) + assert(SinceKotlinVersion("1.0") < SinceKotlinVersion("2.0")) + assert(SinceKotlinVersion("1.0") < SinceKotlinVersion("2.2")) + } + + @Test + fun `rendered SinceKotlin custom tag for typealias, extensions, functions, properties`() { + val writerPlugin = TestOutputWriterPlugin() + + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@SinceKotlin("1.5") + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |@SinceKotlin("1.5") + |fun String.extension(abc: String): String { + | return "My precious " + abc + |} + |@SinceKotlin("1.5") + |typealias Str = String + |@SinceKotlin("1.5") + |val str = "str" + """.trimIndent(), + testConfiguration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.renderedContent("root/test/index.html") + assert(content.getElementsContainingOwnText("Since Kotlin").count() == 4) + } + } + } + + @Test + fun `should propagate SinceKotlin`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@SinceKotlin("1.5") + |class A { + | fun ring(abc: String): String { + | return "My precious " + abc + | } + |} + """.trimIndent(), testConfiguration + ) { + documentablesTransformationStage = { module -> + @Suppress("UNCHECKED_CAST") val funcs = module.children.single { it.name == "test" } + .children.single { it.name == "A" } + .children.filter { it.name == "ring" && it is DFunction } as List<DFunction> + with(funcs) { + val sinceKotlin = mapOf( + Platform.jvm to SinceKotlinVersion("1.5"), + ) + + for(i in sinceKotlin) { + val tag = + find { it.sourceSets.first().analysisPlatform == i.key }?.documentation?.values?.first() + ?.dfs { it is CustomTagWrapper && it.name == "Since Kotlin" } + .assertNotNull("SinceKotlin[${i.key}]") + assertEquals((tag.children.first() as Text).body, i.value.toString()) + } + } + } + } + } + + @Test + fun `mpp fun without SinceKotlin annotation`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "native" + } + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "common" + } + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "js" + } + } + } + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |fun ring(abc: String): String { + | return "My precious " + abc + |} + """.trimIndent(), configuration + ) { + documentablesTransformationStage = { module -> + @Suppress("UNCHECKED_CAST") val funcs = module.children.single { it.name == "test" } + .children.filter { it.name == "ring" && it is DFunction } as List<DFunction> + with(funcs) { + val sinceKotlin = mapOf( + Platform.common to SinceKotlinVersion("1.2"), + Platform.jvm to SinceKotlinVersion("1.0"), + Platform.js to SinceKotlinVersion("1.1"), + Platform.native to SinceKotlinVersion("1.3") + ) + + for(i in sinceKotlin) { + val tag = + find { it.sourceSets.first().analysisPlatform == i.key }?.documentation?.values?.first() + ?.dfs { it is CustomTagWrapper && it.name == "Since Kotlin" } + .assertNotNull("SinceKotlin[${i.key}]") + assertEquals((tag.children.first() as Text).body, i.value.toString()) + } + } + } + } + } + + @Test + fun `mpp fun with SinceKotlin annotation`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "native" + } + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "common" + } + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "js" + } + } + } + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |/** dssdd */ + |@SinceKotlin("1.3") + |fun ring(abc: String): String { + | return "My precious " + abc + |} + """.trimIndent(), configuration + ) { + documentablesTransformationStage = { module -> + @Suppress("UNCHECKED_CAST") val funcs = module.children.single { it.name == "test" } + .children.filter { it.name == "ring" && it is DFunction } as List<DFunction> + with(funcs) { + val sinceKotlin = mapOf( + Platform.common to SinceKotlinVersion("1.3"), + Platform.jvm to SinceKotlinVersion("1.3"), + Platform.js to SinceKotlinVersion("1.3"), + Platform.native to SinceKotlinVersion("1.3") + ) + + for(i in sinceKotlin) { + val tag = + find { it.sourceSets.first().analysisPlatform == i.key }?.documentation?.values?.first() + ?.dfs { it is CustomTagWrapper && it.name == "Since Kotlin" } + .assertNotNull("SinceKotlin[${i.key}]") + assertEquals((tag.children.first() as Text).body, i.value.toString()) + } + } + } + } + } + @Test - fun `function with since kotlin annotation`() { + fun `should do not render since kotlin tag when flag is unset`() { + clearSystemProperty() testInline( """ |/src/main/kotlin/test/source.kt |