From 8e5c63d035ef44a269b8c43430f43f5c8eebfb63 Mon Sep 17 00:00:00 2001 From: Ignat Beresnev Date: Fri, 10 Nov 2023 11:46:54 +0100 Subject: Restructure the project to utilize included builds (#3174) * Refactor and simplify artifact publishing * Update Gradle to 8.4 * Refactor and simplify convention plugins and build scripts Fixes #3132 --------- Co-authored-by: Adam <897017+aSemy@users.noreply.github.com> Co-authored-by: Oleg Yukhnevich --- .../kotlin/content/ContentInDescriptionTest.kt | 142 ++ .../src/test/kotlin/content/HighlightingTest.kt | 83 ++ .../annotations/ContentForAnnotationsTest.kt | 351 +++++ .../content/annotations/FileLevelJvmNameTest.kt | 115 ++ .../content/annotations/JavaDeprecatedTest.kt | 144 ++ .../content/annotations/KotlinDeprecatedTest.kt | 401 +++++ .../kotlin/content/annotations/SinceKotlinTest.kt | 350 +++++ .../content/exceptions/ContentForExceptions.kt | 439 ++++++ .../content/functions/ContentForBriefTest.kt | 388 +++++ .../content/functions/ContentForConstructors.kt | 53 + .../content/inheritors/ContentForInheritorsTest.kt | 499 +++++++ .../kotlin/content/params/ContentForParamsTest.kt | 1529 ++++++++++++++++++++ .../ContentForClassWithParamsAndPropertiesTest.kt | 272 ++++ .../content/receiver/ContentForReceiverTest.kt | 61 + .../content/samples/ContentForSamplesTest.kt | 207 +++ .../content/seealso/ContentForSeeAlsoTest.kt | 866 +++++++++++ .../signatures/ConstructorsSignaturesTest.kt | 469 ++++++ .../content/signatures/ContentForSignaturesTest.kt | 515 +++++++ .../kotlin/content/typealiases/TypealiasTest.kt | 83 ++ 19 files changed, 6967 insertions(+) create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/ContentInDescriptionTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/HighlightingTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/JavaDeprecatedTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/KotlinDeprecatedTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/SinceKotlinTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/exceptions/ContentForExceptions.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/functions/ContentForBriefTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/functions/ContentForConstructors.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/inheritors/ContentForInheritorsTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/params/ContentForParamsTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/properties/ContentForClassWithParamsAndPropertiesTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/receiver/ContentForReceiverTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/samples/ContentForSamplesTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/signatures/ContentForSignaturesTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/content/typealiases/TypealiasTest.kt (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/content') diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/ContentInDescriptionTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/ContentInDescriptionTest.kt new file mode 100644 index 00000000..a278795d --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/ContentInDescriptionTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.doc.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ContentInDescriptionTest : BaseAbstractTest() { + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + classpath += jvmStdlibPath!! + } + } + } + + val expectedDescription = Description( + CustomDocTag( + listOf( + P( + listOf( + Text("Hello World! Docs with period issue, e.g."), + Text(String(Character.toChars(160)), params = mapOf("content-type" to "html")), + Text("this.") + ) + ) + ), + params = emptyMap(), + name = "MARKDOWN_FILE" + ) + ) + + @Test + fun `nbsp is handled as code in kotlin`() { + testInline( + """ + |/src/main/kotlin/sample/ParentKt.kt + |package sample; + |/** + | * Hello World! Docs with period issue, e.g. this. + | */ + |public class ParentKt { + |} + """.trimIndent(), configuration + ) { + documentablesMergingStage = { + val classlike = it.packages.flatMap { it.classlikes }.find { it.name == "ParentKt" } + + assertTrue(classlike != null) + assertEquals(expectedDescription, classlike.documentation.values.first().children.first()) + } + } + } + + @Test + fun `nbsp is handled as code in java`() { + testInline( + """ + |/src/main/kotlin/sample/Parent.java + |package sample; + |/** + | * Hello World! Docs with period issue, e.g. this. + | */ + |public class Parent { + |} + """.trimIndent(), configuration + ) { + documentablesMergingStage = { + val classlike = it.packages.flatMap { it.classlikes }.find { it.name == "Parent" } + + assertTrue(classlike != null) + assertEquals(expectedDescription, classlike.documentation.values.first().children.first()) + } + } + } + + @Test + fun `same documentation in java and kotlin when nbsp is present`() { + testInline( + """ + |/src/main/kotlin/sample/Parent.java + |package sample; + |/** + | * Hello World! Docs with period issue, e.g. this. + | */ + |public class Parent { + |} + | + |/src/main/kotlin/sample/ParentKt.kt + |package sample; + |/** + | * Hello World! Docs with period issue, e.g. this. + | */ + |public class ParentKt { + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + val java = module.packages.flatMap { it.classlikes }.first { it.name == "Parent" } + val kotlin = module.packages.flatMap { it.classlikes }.first { it.name == "ParentKt" } + + assertEquals(java.documentation.values.first(), kotlin.documentation.values.first()) + } + } + } + + @Test + fun `text surrounded by angle brackets is not removed`() { + testInline( + """ + |/src/main/kotlin/sample/Foo.kt + |package sample + |/** + | * My example `CodeInline` + | * ``` + | * CodeBlock + | * ``` + | */ + |class Foo { + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + val cls = module.packages.flatMap { it.classlikes }.first { it.name == "Foo" } + val documentation = cls.documentation.values.first() + val docTags = documentation.children.single().root.children + + assertEquals("CodeInline", ((docTags[0].children[1] as CodeInline).children.first() as Text).body) + assertEquals("CodeBlock", ((docTags[1] as CodeBlock).children.first() as Text).body) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/HighlightingTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/HighlightingTest.kt new file mode 100644 index 00000000..a7fb2bde --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/HighlightingTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.pages.* +import kotlin.test.Test +import kotlin.test.assertTrue + +class HighlightingTest : BaseAbstractTest() { + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOf(commonStdlibPath!!, jvmStdlibPath!!) + externalDocumentationLinks = listOf(stdlibExternalDocumentationLink) + } + } + } + + @Test + fun `open suspend fun`() { + testInline( + """ + |/src/main/kotlin/test/Test.kt + |package example + | + | open suspend fun simpleFun(): String = "Celebrimbor" + """, + configuration + ) { + pagesTransformationStage = { module -> + val symbol = (module.dfs { it.name == "simpleFun" } as MemberPageNode).content + .dfs { it is ContentGroup && it.dci.kind == ContentKind.Symbol } + val children = symbol?.children + + for (it in listOf( + Pair(0, TokenStyle.Keyword), Pair(1, TokenStyle.Keyword), Pair(2, TokenStyle.Keyword), + Pair(4, TokenStyle.Punctuation), Pair(5, TokenStyle.Punctuation), Pair(6, TokenStyle.Operator) + )) + assertTrue(children?.get(it.first)?.style?.contains(it.second) == true) + assertTrue(children?.get(3)?.children?.first()?.style?.contains(TokenStyle.Function) == true) + } + } + } + + @Test + fun `plain typealias of plain class with annotation`() { + testInline( + """ + |/src/main/kotlin/common/Test.kt + |package example + | + |@MustBeDocumented + |@Target(AnnotationTarget.TYPEALIAS) + |annotation class SomeAnnotation + | + |@SomeAnnotation + |typealias PlainTypealias = Int + | + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { module -> + val symbol = (module.dfs { it.name == "example" } as PackagePageNode).content + .dfs { it is ContentGroup && it.dci.kind == ContentKind.Symbol } + val children = symbol?.children + + for (it in listOf( + Pair(1, TokenStyle.Keyword), Pair(3, TokenStyle.Operator) + )) + assertTrue(children?.get(it.first)?.style?.contains(it.second) == true) + val annotation = children?.first()?.children?.first() + + assertTrue(annotation?.children?.get(0)?.style?.contains(TokenStyle.Annotation) == true) + assertTrue(annotation?.children?.get(1)?.children?.first()?.style?.contains(TokenStyle.Annotation) == true) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt new file mode 100644 index 00000000..7293b53c --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt @@ -0,0 +1,351 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.annotations + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.utils.firstNotNullOfOrNull +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.ContentText +import org.jetbrains.dokka.pages.MemberPageNode +import org.jetbrains.dokka.pages.PackagePageNode +import utils.ParamAttributes +import utils.assertNotNull +import utils.bareSignature +import utils.propertySignature +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + + +class ContentForAnnotationsTest : BaseAbstractTest() { + + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + classpath += jvmStdlibPath!! + } + } + } + + @Test + fun `function with documented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, + | AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD + |) + |@Retention(AnnotationRetention.SOURCE) + |@MustBeDocumented + |annotation class Fancy + | + | + |@Fancy + |fun function(@Fancy abc: String): String { + | return "Hello, " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + mapOf("Fancy" to emptySet()), + "", + "", + emptySet(), + "function", + "String", + "abc" to ParamAttributes(mapOf("Fancy" to emptySet()), emptySet(), "String") + ) + } + } + } + + } + } + } + } + + @Test + fun `function with undocumented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, + | AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD + |) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + | + |@Fancy + |fun function(@Fancy abc: String): String { + | return "Hello, " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + "String", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + + } + } + } + } + + @Test + fun `property with undocumented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@Suppress + |val property: Int = 6 + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature(emptyMap(), "", "", emptySet(), "val", "property", "Int", "6") + } + } + } + } + + @Test + fun `property with documented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@MustBeDocumented + |annotation class Fancy + | + |@Fancy + |val property: Int = 6 + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature(mapOf("Fancy" to emptySet()), "", "", emptySet(), "val", "property", "Int", "6") + } + } + } + } + + + @Test + fun `rich documented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@MustBeDocumented + |@Retention(AnnotationRetention.SOURCE) + |@Target(AnnotationTarget.PROPERTY) + |annotation class BugReport( + | val assignedTo: String = "[none]", + | val testCase: KClass = ABC::class, + | val status: Status = Status.UNCONFIRMED, + | val ref: Reference = Reference(value = 1), + | val reportedBy: Array, + | val showStopper: Boolean = false + | val previousReport: BugReport? + |) { + | enum class Status { + | UNCONFIRMED, CONFIRMED, FIXED, NOTABUG + | } + | class ABC + |} + |annotation class Reference(val value: Long) + |annotation class ReferenceReal(val value: Double) + | + | + |@BugReport( + | assignedTo = "me", + | testCase = BugReport.ABC::class, + | status = BugReport.Status.FIXED, + | ref = Reference(value = 2u), + | reportedBy = [Reference(value = 2UL), Reference(value = 4L), + | ReferenceReal(value = 4.9), ReferenceReal(value = 2f)], + | showStopper = true, + | previousReport = null + |) + |val ltint: Int = 5 + """.trimIndent(), testConfiguration + ) { + documentablesCreationStage = { modules -> + + fun expectedAnnotationValue(name: String, value: AnnotationParameterValue) = AnnotationValue(Annotations.Annotation( + dri = DRI("test", name), + params = mapOf("value" to value), + scope = Annotations.AnnotationScope.DIRECT, + mustBeDocumented = false + )) + val property = modules.flatMap { it.packages }.flatMap { it.properties }.first() + val annotation = property.extra[Annotations]?.let { + it.directAnnotations.entries.firstNotNullOfOrNull { (_, annotations) -> annotations.firstOrNull() } + } + val annotationParams = annotation?.params ?: emptyMap() + + assertEquals(expectedAnnotationValue("Reference", IntValue(2)), annotationParams["ref"]) + + val reportedByParam = ArrayValue(listOf( + expectedAnnotationValue("Reference", LongValue(2)), + expectedAnnotationValue("Reference", LongValue(4)), + expectedAnnotationValue("ReferenceReal", DoubleValue(4.9)), + expectedAnnotationValue("ReferenceReal", FloatValue(2f)) + )) + assertEquals(reportedByParam, annotationParams["reportedBy"]) + assertEquals(BooleanValue(true), annotationParams["showStopper"]) + assertEquals(NullValue, annotationParams["previousReport"]) + } + + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature( + mapOf( + "BugReport" to setOf( + "assignedTo", + "testCase", + "status", + "ref", + "reportedBy", + "showStopper", + "previousReport" + ) + ), "", "", emptySet(), "val", "ltint", "Int", "5" + ) + } + } + } + } + + @Test + fun `JvmName for property with setter and getter`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + |@get:JvmName("xd") + |@set:JvmName("asd") + |var property: String + | get() = "" + | set(value) {} + """.trimIndent(), testConfiguration + ) { + documentablesCreationStage = { modules -> + fun expectedAnnotation(name: String) = Annotations.Annotation( + dri = DRI("kotlin.jvm", "JvmName"), + params = mapOf("name" to StringValue(name)), + scope = Annotations.AnnotationScope.DIRECT, + mustBeDocumented = true + ) + + val property = modules.flatMap { it.packages }.flatMap { it.properties }.first() + val getterAnnotation = property.getter?.extra?.get(Annotations)?.let { + it.directAnnotations.entries.firstNotNullOfOrNull { (_, annotations) -> annotations.firstOrNull() } + } + val setterAnnotation = property.getter?.extra?.get(Annotations)?.let { + it.directAnnotations.entries.firstNotNullOfOrNull { (_, annotations) -> annotations.firstOrNull() } + } + + assertEquals(expectedAnnotation("xd"), getterAnnotation) + assertTrue(getterAnnotation?.mustBeDocumented!!) + assertEquals(Annotations.AnnotationScope.DIRECT, getterAnnotation.scope) + + assertEquals(expectedAnnotation("asd"), setterAnnotation) + assertTrue(setterAnnotation?.mustBeDocumented!!) + assertEquals(Annotations.AnnotationScope.DIRECT, setterAnnotation.scope) + } + } + } + + @Test + fun `annotated bounds in Kotlin`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |@MustBeDocumented + |@Target(AnnotationTarget.TYPE_PARAMETER) + |annotation class Hello(val bar: String) + |fun foo(arg: String): List = TODO() + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { root -> + val fooPage = root.dfs { it.name == "foo" } as MemberPageNode + fooPage.content.dfs { it is ContentText && it.text == "Hello" }.assertNotNull() + } + } + } + + @Test + fun `annotated bounds in Java`() { + testInline( + """ + |/src/main/java/demo/AnnotationTest.java + |package demo; + |import java.lang.annotation.*; + |import java.util.List; + |@Documented + |@Target({ElementType.TYPE_USE, ElementType.TYPE}) + |@interface Hello { + | public String bar() default ""; + |} + |public class AnnotationTest { + | public List foo() { + | return null; + | } + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { root -> + val fooPage = root.dfs { it.name == "foo" } as MemberPageNode + fooPage.content.dfs { it is ContentText && it.text == "Hello" }.assertNotNull() + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt new file mode 100644 index 00000000..5809d7df --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.annotations + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.Annotations +import org.jetbrains.dokka.model.StringValue +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.assertEquals + +class FileLevelJvmNameTest : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + classpath += jvmStdlibPath!! + } + } + } + + companion object { + private const val functionTest = + """ + |/src/main/kotlin/test/source.kt + |@file:JvmName("CustomJvmName") + |package test + | + |fun function(abc: String): String { + | return "Hello, " + abc + |} + """ + + private const val extensionFunctionTest = + """ + |/src/main/kotlin/test/source.kt + |@file:JvmName("CustomJvmName") + |package test + | + |fun String.function(abc: String): String { + | return "Hello, " + abc + |} + """ + + private const val propertyTest = + """ + |/src/main/kotlin/test/source.kt + |@file:JvmName("CustomJvmName") + |package test + | + |val property: String + | get() = "" + """ + + private const val extensionPropertyTest = + """ + |/src/main/kotlin/test/source.kt + |@file:JvmName("CustomJvmName") + |package test + | + |val String.property: String + | get() = "" + """ + } + + @ParameterizedTest + @ValueSource(strings = [functionTest, extensionFunctionTest]) + fun `jvm name should be included in functions extra`(query: String) { + testInline( + query.trimIndent(), testConfiguration + ) { + documentablesCreationStage = { modules -> + val expectedAnnotation = Annotations.Annotation( + dri = DRI("kotlin.jvm", "JvmName"), + params = mapOf("name" to StringValue("CustomJvmName")), + scope = Annotations.AnnotationScope.FILE, + mustBeDocumented = true + ) + val function = modules.flatMap { it.packages }.first().functions.first() + val annotation = function.extra[Annotations]?.fileLevelAnnotations?.entries?.first()?.value?.single() + assertEquals(emptyMap(), function.extra[Annotations]?.directAnnotations) + assertEquals(expectedAnnotation, annotation) + assertEquals(expectedAnnotation.scope, annotation?.scope) + assertEquals(expectedAnnotation.mustBeDocumented, annotation?.mustBeDocumented) + } + } + } + + @ParameterizedTest + @ValueSource(strings = [propertyTest, extensionPropertyTest]) + fun `jvm name should be included in properties extra`(query: String) { + testInline( + query.trimIndent(), testConfiguration + ) { + documentablesCreationStage = { modules -> + val expectedAnnotation = Annotations.Annotation( + dri = DRI("kotlin.jvm", "JvmName"), + params = mapOf("name" to StringValue("CustomJvmName")), + scope = Annotations.AnnotationScope.FILE, + mustBeDocumented = true + ) + val properties = modules.flatMap { it.packages }.first().properties.first() + val annotation = properties.extra[Annotations]?.fileLevelAnnotations?.entries?.first()?.value?.single() + assertEquals(emptyMap(), properties.extra[Annotations]?.directAnnotations) + assertEquals(expectedAnnotation, annotation) + assertEquals(expectedAnnotation.scope, annotation?.scope) + assertEquals(expectedAnnotation.mustBeDocumented, annotation?.mustBeDocumented) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/JavaDeprecatedTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/JavaDeprecatedTest.kt new file mode 100644 index 00000000..5a2ff93e --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/JavaDeprecatedTest.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.annotations + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.transformers.documentables.deprecatedAnnotation +import org.jetbrains.dokka.base.transformers.documentables.isDeprecated +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.properties.WithExtraProperties +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.ContentStyle +import utils.pWrapped +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class JavaDeprecatedTest : BaseAbstractTest() { + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `should assert util functions for deprecation`() { + testInline( + """ + |/src/main/kotlin/deprecated/DeprecatedJavaClass.java + |package deprecated + | + |@Deprecated(forRemoval = true) + |public class DeprecatedJavaClass {} + """.trimIndent(), + testConfiguration + ) { + documentablesTransformationStage = { module -> + val deprecatedClass = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "DeprecatedJavaClass" } + + val isDeprecated = (deprecatedClass as WithExtraProperties).isDeprecated() + assertTrue(isDeprecated) + + val deprecatedAnnotation = (deprecatedClass as WithExtraProperties).deprecatedAnnotation + assertNotNull(deprecatedAnnotation) + + assertTrue(deprecatedAnnotation.isDeprecated()) + assertEquals("java.lang", deprecatedAnnotation.dri.packageName) + assertEquals("Deprecated", deprecatedAnnotation.dri.classNames) + } + } + } + + @Test + fun `should change deprecated header if marked for removal`() { + testInline( + """ + |/src/main/kotlin/deprecated/DeprecatedJavaClass.java + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated(forRemoval = true) + |public class DeprecatedJavaClass {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val deprecatedJavaClass = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "DeprecatedJavaClass" } as ContentPage + + deprecatedJavaClass.content.assertNode { + group { + header(1) { +"DeprecatedJavaClass" } + platformHinted { + skipAllNotMatching() + group { + header(3) { + +"Deprecated (for removal)" + } + } + group { pWrapped("Average function description") } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `should add footnote for 'since' param`() { + testInline( + """ + |/src/main/kotlin/deprecated/DeprecatedJavaClass.java + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated(since = "11") + |public class DeprecatedJavaClass {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val deprecatedJavaClass = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "DeprecatedJavaClass" } as ContentPage + + deprecatedJavaClass.content.assertNode { + group { + header(1) { +"DeprecatedJavaClass" } + platformHinted { + skipAllNotMatching() + group { + header(3) { + +"Deprecated" + } + group { + check { assertEquals(ContentStyle.Footnote, this.style.firstOrNull()) } + +"Since version 11" + } + } + group { pWrapped("Average function description") } + } + } + skipAllNotMatching() + } + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/KotlinDeprecatedTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/KotlinDeprecatedTest.kt new file mode 100644 index 00000000..7612aff8 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/KotlinDeprecatedTest.kt @@ -0,0 +1,401 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.annotations + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.transformers.documentables.deprecatedAnnotation +import org.jetbrains.dokka.base.transformers.documentables.isDeprecated +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.properties.WithExtraProperties +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.ContentStyle +import utils.ParamAttributes +import utils.bareSignature +import utils.pWrapped +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + + +class KotlinDeprecatedTest : BaseAbstractTest() { + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + analysisPlatform = "jvm" + } + } + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `should assert util functions for deprecation`() { + testInline( + """ + |/src/main/kotlin/kotlin/KotlinFile.kt + |package deprecated + | + |@Deprecated( + | message = "Fancy message" + |) + |fun simpleFunction() {} + """.trimIndent(), + testConfiguration + ) { + documentablesTransformationStage = { module -> + val deprecatedFunction = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "simpleFunction" } + + val isDeprecated = (deprecatedFunction as WithExtraProperties).isDeprecated() + assertTrue(isDeprecated) + + val deprecatedAnnotation = (deprecatedFunction as WithExtraProperties).deprecatedAnnotation + assertNotNull(deprecatedAnnotation) + + assertTrue(deprecatedAnnotation.isDeprecated()) + assertEquals("kotlin", deprecatedAnnotation.dri.packageName) + assertEquals("Deprecated", deprecatedAnnotation.dri.classNames) + } + } + } + + @Test + fun `should change header if deprecation level is not default`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated( + | message = "Reason for deprecation bla bla", + | level = DeprecationLevel.ERROR + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated (with error)" + } + p { + +"Reason for deprecation bla bla" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } + + @Test + fun `should display repalceWith param with imports as code blocks`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated( + | message = "Reason for deprecation bla bla", + | replaceWith = ReplaceWith( + | "newShinyFunction(typedParam, someLiteral, SomeNewType())", + | imports = [ + | "com.example.dokka.debug.newShinyFunction", + | "com.example.dokka.debug.SomeOldType", + | "com.example.dokka.debug.SomeNewType", + | ] + | ), + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated" + } + p { + +"Reason for deprecation bla bla" + } + + header(4) { + +"Replace with" + } + codeBlock { + +"import com.example.dokka.debug.newShinyFunction" + br() + +"import com.example.dokka.debug.SomeOldType" + br() + +"import com.example.dokka.debug.SomeNewType" + br() + } + codeBlock { + +"newShinyFunction(typedParam, someLiteral, SomeNewType())" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } + + @Test + fun `should add footnote for DeprecatedSinceKotlin annotation`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package deprecated + | + |/** + | * Average function description + | */ + |@DeprecatedSinceKotlin( + | warningSince = "1.4", + | errorSince = "1.5", + | hiddenSince = "1.6" + |) + |@Deprecated( + | message = "Deprecation reason bla bla" + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated" + } + group { + check { assertEquals(ContentStyle.Footnote, this.style.firstOrNull()) } + p { + +"Warning since 1.4" + } + p { + +"Error since 1.5" + } + p { + +"Hidden since 1.6" + } + } + p { + +"Deprecation reason bla bla" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } + + @Test + fun `should generate deprecation block with all parameters present and long description`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated( + | message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vel vulputate risus. " + + | "Etiam dictum odio vel vulputate auctor.Nulla facilisi. Duis ullamcorper ullamcorper lectus " + + | "nec rutrum. Quisque eu risus eu purus bibendum ultricies. Maecenas tincidunt dui in sodales " + + | "faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id sem felis. " + + | "Praesent et libero lacinia, egestas libero in, ultrices lectus. Suspendisse eget volutpat " + + | "velit. Phasellus laoreet mi eu egestas mattis.", + | replaceWith = ReplaceWith( + | "newShinyFunction(typedParam, someLiteral, SomeNewType())", + | imports = [ + | "com.example.dokka.debug.newShinyFunction", + | "com.example.dokka.debug.SomeOldType", + | "com.example.dokka.debug.SomeNewType", + | ] + | ), + | level = DeprecationLevel.ERROR + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated (with error)" + } + p { + +("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "Maecenas vel vulputate risus. Etiam dictum odio vel " + + "vulputate auctor.Nulla facilisi. Duis ullamcorper " + + "ullamcorper lectus nec rutrum. Quisque eu risus eu " + + "purus bibendum ultricies. Maecenas tincidunt dui in sodales faucibus. " + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "Proin id sem felis. Praesent et libero lacinia, egestas " + + "libero in, ultrices lectus. Suspendisse eget volutpat velit. " + + "Phasellus laoreet mi eu egestas mattis.") + } + header(4) { + +"Replace with" + } + codeBlock { + +"import com.example.dokka.debug.newShinyFunction" + br() + +"import com.example.dokka.debug.SomeOldType" + br() + +"import com.example.dokka.debug.SomeNewType" + br() + } + codeBlock { + +"newShinyFunction(typedParam, someLiteral, SomeNewType())" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/SinceKotlinTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/SinceKotlinTest.kt new file mode 100644 index 00000000..6ee95bbd --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/SinceKotlinTest.kt @@ -0,0 +1,350 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.annotations + +import matchers.content.* +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 signatures.AbstractRenderingTest +import utils.* +import kotlin.test.* + + +class SinceKotlinTest : AbstractRenderingTest() { + + val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + analysisPlatform = "jvm" + } + } + } + + @BeforeTest + fun setSystemProperty() { + System.setProperty(SinceKotlinTransformer.SHOULD_DISPLAY_SINCE_KOTLIN_SYS_PROP, "true") + } + @AfterTest + fun clearSystemProperty() { + System.clearProperty(SinceKotlinTransformer.SHOULD_DISPLAY_SINCE_KOTLIN_SYS_PROP) + } + + @Test + fun versionsComparing() { + assertTrue(SinceKotlinVersion("1.0").compareTo(SinceKotlinVersion("1.0")) == 0) + assertTrue(SinceKotlinVersion("1.0.0").compareTo(SinceKotlinVersion("1")) == 0) + assertTrue(SinceKotlinVersion("1.0") >= SinceKotlinVersion("1.0")) + assertTrue(SinceKotlinVersion("1.1") > SinceKotlinVersion("1")) + assertTrue(SinceKotlinVersion("1.0") < SinceKotlinVersion("2.0")) + assertTrue(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") + assertEquals(4, content.getElementsContainingOwnText("Since Kotlin").count()) + } + } + } + + @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 + 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/jvm/") + analysisPlatform = "jvm" + } + sourceSet { + sourceRoots = listOf("src/native/") + analysisPlatform = "native" + name = "native" + } + sourceSet { + sourceRoots = listOf("src/common/") + analysisPlatform = "common" + name = "common" + } + sourceSet { + sourceRoots = listOf("src/js/") + analysisPlatform = "js" + name = "js" + } + sourceSet { + sourceRoots = listOf("src/wasm/") + analysisPlatform = "wasm" + name = "wasm" + } + } + } + testInline( + """ + |/src/jvm/kotlin/test/source.kt + |package test + | + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/native/kotlin/test/source.kt + |package test + | + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/common/kotlin/test/source.kt + |package test + | + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/js/kotlin/test/source.kt + |package test + | + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/wasm/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 + with(funcs) { + val sinceKotlin = mapOf( + Platform.common to SinceKotlinVersion("1.0"), + Platform.jvm to SinceKotlinVersion("1.0"), + Platform.js to SinceKotlinVersion("1.1"), + Platform.native to SinceKotlinVersion("1.3"), + Platform.wasm to SinceKotlinVersion("1.8"), + ) + + 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/jvm/") + classpath = listOfNotNull(jvmStdlibPath) + analysisPlatform = "jvm" + } + sourceSet { + sourceRoots = listOf("src/native/") + analysisPlatform = "native" + name = "native" + } + sourceSet { + sourceRoots = listOf("src/common/") + classpath = listOfNotNull(commonStdlibPath) + analysisPlatform = "common" + name = "common" + } + sourceSet { + sourceRoots = listOf("src/js/") + classpath = listOfNotNull(jsStdlibPath) + analysisPlatform = "js" + name = "js" + } +