diff options
Diffstat (limited to 'dokka-subprojects/analysis-kotlin-api/src')
48 files changed, 2519 insertions, 0 deletions
diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/KotlinAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/KotlinAnalysisPlugin.kt new file mode 100644 index 00000000..7d434bd5 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/KotlinAnalysisPlugin.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin + +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.plugability.DokkaPluginApiPreview +import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement + +public class KotlinAnalysisPlugin : DokkaPlugin() { + + /* + * This is where stable public API will go. + * + * No stable public API for now. + */ + + @OptIn(DokkaPluginApiPreview::class) + override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/DocumentableSourceLanguageParser.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/DocumentableSourceLanguageParser.kt new file mode 100644 index 00000000..116adb06 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/DocumentableSourceLanguageParser.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.internal + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.WithSources + +@InternalDokkaApi +public enum class DocumentableLanguage { + JAVA, KOTLIN +} + +@InternalDokkaApi +public interface DocumentableSourceLanguageParser { + public fun getLanguage(documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet): DocumentableLanguage? +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/ExternalDocumentablesProvider.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/ExternalDocumentablesProvider.kt new file mode 100644 index 00000000..7c564880 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/ExternalDocumentablesProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.internal + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.DClasslike + +/** + * Service that can be queried with [DRI] and source set to obtain a documentable for classlike. + * + * There are some cases when there is a need to process documentables of classlikes that were not defined + * in the project itself but are somehow related to the symbols defined in the documented project (e.g. are supertypes + * of classes defined in project). + */ +@InternalDokkaApi +public fun interface ExternalDocumentablesProvider { + + /** + * Returns [DClasslike] matching provided [DRI] in specified source set. + * + * Result is null if compiler haven't generated matching class descriptor. + */ + public fun findClasslike(dri: DRI, sourceSet: DokkaConfiguration.DokkaSourceSet): DClasslike? +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/FullClassHierarchyBuilder.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/FullClassHierarchyBuilder.kt new file mode 100644 index 00000000..5b975fd8 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/FullClassHierarchyBuilder.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.internal + +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.model.SourceSetDependent + +@InternalDokkaApi +public typealias Supertypes = List<DRI> + +@InternalDokkaApi +public typealias ClassHierarchy = SourceSetDependent<Map<DRI, Supertypes>> + +@InternalDokkaApi +public interface FullClassHierarchyBuilder { + public suspend fun build(module: DModule): ClassHierarchy +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InheritanceBuilder.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InheritanceBuilder.kt new file mode 100644 index 00000000..32077cde --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InheritanceBuilder.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.internal + +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.Documentable + +@InternalDokkaApi +public interface InheritanceBuilder { + public fun build(documentables: Map<DRI, Documentable>): List<InheritanceNode> +} + +@InternalDokkaApi +public data class InheritanceNode( + val dri: DRI, + val children: List<InheritanceNode> = emptyList(), + val interfaces: List<DRI> = emptyList(), + val isInterface: Boolean = false +) { + override fun equals(other: Any?): Boolean = other is InheritanceNode && other.dri == dri + override fun hashCode(): Int = dri.hashCode() +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt new file mode 100644 index 00000000..0ef1399a --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.internal + +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.plugability.DokkaPluginApiPreview +import org.jetbrains.dokka.plugability.ExtensionPoint +import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement + +/** + * A plugin for internal use, has no stable public API and thus must not be used by third party, + * external plugins. If you need any of the given API stabilized, please create an issue describing your use case. + */ +@InternalDokkaApi +public class InternalKotlinAnalysisPlugin : DokkaPlugin() { + + public val fullClassHierarchyBuilder: ExtensionPoint<FullClassHierarchyBuilder> by extensionPoint() + + public val syntheticDocumentableDetector: ExtensionPoint<SyntheticDocumentableDetector> by extensionPoint() + + public val moduleAndPackageDocumentationReader: ExtensionPoint<ModuleAndPackageDocumentationReader> by extensionPoint() + + public val kotlinToJavaService: ExtensionPoint<KotlinToJavaService> by extensionPoint() + + public val inheritanceBuilder: ExtensionPoint<InheritanceBuilder> by extensionPoint() + + public val externalDocumentablesProvider: ExtensionPoint<ExternalDocumentablesProvider> by extensionPoint() + + public val documentableSourceLanguageParser: ExtensionPoint<DocumentableSourceLanguageParser> by extensionPoint() + + public val sampleProviderFactory: ExtensionPoint<SampleProviderFactory> by extensionPoint() + + @OptIn(DokkaPluginApiPreview::class) + override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/KotlinToJavaService.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/KotlinToJavaService.kt new file mode 100644 index 00000000..1ce47031 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/KotlinToJavaService.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.internal + +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.links.DRI + +@InternalDokkaApi +public interface KotlinToJavaService { + /** + * E.g. + * kotlin.Throwable -> java.lang.Throwable + * kotlin.Int -> java.lang.Integer + * kotlin.Int.Companion -> kotlin.jvm.internal.IntCompanionObject + * kotlin.Nothing -> java.lang.Void + * kotlin.IntArray -> null + * kotlin.Function3 -> kotlin.jvm.functions.Function3 + * kotlin.coroutines.SuspendFunction3 -> kotlin.jvm.functions.Function4 + * kotlin.Function42 -> kotlin.jvm.functions.FunctionN + * kotlin.coroutines.SuspendFunction42 -> kotlin.jvm.functions.FunctionN + * kotlin.reflect.KFunction3 -> kotlin.reflect.KFunction + * kotlin.reflect.KSuspendFunction3 -> kotlin.reflect.KFunction + * kotlin.reflect.KFunction42 -> kotlin.reflect.KFunction + * kotlin.reflect.KSuspendFunction42 -> kotlin.reflect.KFunction + */ + public fun findAsJava(kotlinDri: DRI): DRI? +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/ModuleAndPackageDocumentationReader.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/ModuleAndPackageDocumentationReader.kt new file mode 100644 index 00000000..70419e0e --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/ModuleAndPackageDocumentationReader.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.internal + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.model.DPackage +import org.jetbrains.dokka.model.SourceSetDependent +import org.jetbrains.dokka.model.doc.DocumentationNode + +@InternalDokkaApi +public interface ModuleAndPackageDocumentationReader { + public fun read(module: DModule): SourceSetDependent<DocumentationNode> + public fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> + public fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode? +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider.kt new file mode 100644 index 00000000..472d17f0 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.internal + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.InternalDokkaApi + +@InternalDokkaApi +public interface SampleProviderFactory { + /** + * [SampleProvider] is a short-lived closeable instance. + * It assumes that [SampleProvider] scope of use is not big. + * Otherwise, it can lead to high memory consumption / leaks during Dokka running. + */ + public fun build(): SampleProvider +} + +/** + * It is closeable. + * Otherwise, there is a chance of high memory consumption / leak. + * In general case, it creates a separate project to analysis samples directories. + */ +@InternalDokkaApi +public interface SampleProvider: AutoCloseable { + public class SampleSnippet( + public val imports: String, + public val body: String + ) + + /** + * @return [SampleSnippet] or null if it has not found by [fqLink] + */ + public fun getSample(sourceSet: DokkaConfiguration.DokkaSourceSet, fqLink: String): SampleSnippet? +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SyntheticDocumentableDetector.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SyntheticDocumentableDetector.kt new file mode 100644 index 00000000..0a2d64de --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SyntheticDocumentableDetector.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.internal + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.model.Documentable + +// TODO [beresnev] isSynthetic could be a property of Documentable +@InternalDokkaApi +public interface SyntheticDocumentableDetector { + public fun isSynthetic(documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet): Boolean +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/SampleJavaAnalysisTest.kt b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/SampleJavaAnalysisTest.kt new file mode 100644 index 00000000..f6632f60 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/SampleJavaAnalysisTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.jvm.java + +import org.jetbrains.dokka.analysis.test.api.javaTestProject +import org.jetbrains.dokka.analysis.test.api.parse +import kotlin.test.Test +import kotlin.test.assertEquals + +class SampleJavaAnalysisTest { + + /** + * Used as a sample for [javaTestProject] + */ + @Test + fun sample() { + val testProject = javaTestProject { + dokkaConfiguration { + moduleName = "java-module-name-for-unit-test" + + javaSourceSet { + // source-set specific configuration + } + } + javaFile(pathFromSrc = "org/jetbrains/dokka/test/java/Bar.java") { + +""" + public class Bar { + public static void bar() { + System.out.println("Bar"); + } + } + """ + } + } + + val module = testProject.parse() + assertEquals("java-module-name-for-unit-test", module.name) + assertEquals(1, module.packages.size) + + val pckg = module.packages[0] + assertEquals("org.jetbrains.dokka.test.java", pckg.name) + assertEquals(1, pckg.classlikes.size) + + val fooClass = pckg.classlikes[0] + assertEquals("Bar", fooClass.name) + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/kotlin/SampleKotlinJvmAnalysisTest.kt b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/kotlin/SampleKotlinJvmAnalysisTest.kt new file mode 100644 index 00000000..6c73af1f --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/kotlin/SampleKotlinJvmAnalysisTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.jvm.kotlin + +import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject +import org.jetbrains.dokka.analysis.test.api.parse +import kotlin.test.Test +import kotlin.test.assertEquals + +class SampleKotlinJvmAnalysisTest { + + /** + * Used as a sample for [kotlinJvmTestProject] + */ + @Test + fun sample() { + val testProject = kotlinJvmTestProject { + dokkaConfiguration { + moduleName = "kotlin-jvm-module-name-for-unit-test" + + kotlinSourceSet { + // source-set specific configuration + } + } + ktFile(pathFromSrc = "org/jetbrains/dokka/test/kotlin/MyFile.kt") { + +"public class Foo {}" + } + } + + val module = testProject.parse() + assertEquals("kotlin-jvm-module-name-for-unit-test", module.name) + assertEquals(1, module.packages.size) + + val pckg = module.packages[0] + assertEquals("org.jetbrains.dokka.test.kotlin", pckg.name) + assertEquals(1, pckg.classlikes.size) + + val fooClass = pckg.classlikes[0] + assertEquals("Foo", fooClass.name) + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/mixed/SampleMixedJvmAnalysisTest.kt b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/mixed/SampleMixedJvmAnalysisTest.kt new file mode 100644 index 00000000..fec2ceb8 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/mixed/SampleMixedJvmAnalysisTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.jvm.mixed + +import org.jetbrains.dokka.analysis.test.api.mixedJvmTestProject +import org.jetbrains.dokka.analysis.test.api.parse +import kotlin.test.Test +import kotlin.test.assertEquals + +class SampleMixedJvmAnalysisTest { + + /** + * Used as a sample for [mixedJvmTestProject] + */ + @Test + fun sample() { + val testProject = mixedJvmTestProject { + dokkaConfiguration { + moduleName = "mixed-project-module-name-for-unit-test" + + jvmSourceSet { + // source-set specific configuration + } + } + + kotlinSourceDirectory { + ktFile(pathFromSrc = "test/MyFile.kt") { + +"fun foo(): String = \"Foo\"" + } + javaFile(pathFromSrc = "test/MyJavaFileInKotlin.java") { + +""" + public class MyJavaFileInKotlin { + public static void bar() { + System.out.println("Bar"); + } + } + """ + } + } + + javaSourceDirectory { + ktFile(pathFromSrc = "test/MyFile.kt") { + +"fun bar(): String = \"Bar\"" + } + javaFile(pathFromSrc = "test/MyJavaFileInJava.java") { + +""" + public class MyJavaFileInJava { + public static void bar() { + System.out.println("Bar"); + } + } + """ + } + } + } + + val module = testProject.parse() + assertEquals("mixed-project-module-name-for-unit-test", module.name) + assertEquals(1, module.packages.size) + + val pckg = module.packages[0] + assertEquals("test", pckg.name) + + assertEquals(2, pckg.classlikes.size) + assertEquals(2, pckg.functions.size) + + val firstClasslike = pckg.classlikes[0] + assertEquals("MyJavaFileInKotlin", firstClasslike.name) + + val secondClasslike = pckg.classlikes[1] + assertEquals("MyJavaFileInJava", secondClasslike.name) + + val firstFunction = pckg.functions[0] + assertEquals("bar", firstFunction.name) + + val secondFunction = pckg.functions[1] + assertEquals("foo", secondFunction.name) + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/moduledocs/PackageDocumentationAnalysisTest.kt b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/moduledocs/PackageDocumentationAnalysisTest.kt new file mode 100644 index 00000000..55507023 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/moduledocs/PackageDocumentationAnalysisTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.moduledocs + +import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject +import org.jetbrains.dokka.analysis.test.api.useServices +import org.jetbrains.dokka.model.doc.CodeInline +import org.jetbrains.dokka.model.doc.Description +import org.jetbrains.dokka.model.doc.P +import org.jetbrains.dokka.model.doc.Text +import kotlin.test.Test +import kotlin.test.assertEquals + +class PackageDocumentationAnalysisTest { + + @Test + fun `should parse include description for a nested package in kotlin-jvm`() { + val testProject = kotlinJvmTestProject { + dokkaConfiguration { + kotlinSourceSet { + includes = setOf("/documentation/cool-package-description.md") + } + } + + ktFile(pathFromSrc = "org/jetbrains/dokka/pckg/docs/test/TestFile.kt") { + +"class TestFile {}" + } + + mdFile(pathFromProjectRoot = "/documentation/cool-package-description.md") { + +""" + # Package org.jetbrains.dokka.pckg.docs.test + + This is my test description for the package `org.jetbrains.dokka.pckg.docs.test`, + which contains only one file named TestFile.kt. It has one empty class. + """ + } + } + + testProject.useServices { context -> + val pckg = context.module.packages.single { it.name == "org.jetbrains.dokka.pckg.docs.test" } + + val allPackageDocs = moduleAndPackageDocumentationReader.read(pckg) + assertEquals(1, allPackageDocs.size) + + val sourceSetPackageDocs = allPackageDocs.entries.single().value + assertEquals(1, sourceSetPackageDocs.children.size) + + val descriptionTag = sourceSetPackageDocs.children.single() as Description + assertEquals(1, descriptionTag.children.size) + + val paragraphTag = descriptionTag.children.single() as P + assertEquals(3, paragraphTag.children.size) + + val expectedParagraphChildren = listOf( + Text("This is my test description for the package "), + CodeInline(children = listOf(Text( + "org.jetbrains.dokka.pckg.docs.test" + ))), + Text(", which contains only one file named TestFile.kt. It has one empty class.") + ) + assertEquals(expectedParagraphChildren, paragraphTag.children) + } + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/sample/SampleAnalysisTest.kt b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/sample/SampleAnalysisTest.kt new file mode 100644 index 00000000..618e28a8 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/sample/SampleAnalysisTest.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.sample + +import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject +import org.jetbrains.dokka.analysis.test.api.useServices +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class SampleAnalysisTest { + + @Test + fun `should return sources of a kotlin sample`() { + val testProject = kotlinJvmTestProject { + dokkaConfiguration { + kotlinSourceSet { + additionalSourceRoots = setOf("/samples") + } + } + sampleFile("/samples/stringListOf-sample.kt", fqPackageName = "org.jetbrains.dokka.sample.generator") { + +""" + import org.jetbrains.dokka.DokkaConfiguration + import org.jetbrains.dokka.DokkaGenerator + import org.jetbrains.dokka.utilities.DokkaLogger + + fun runGenerator(configuration: DokkaConfiguration, logger: DokkaLogger) { + DokkaGenerator(configuration, logger).generate() + } + """ + } + } + + testProject.useServices { context -> + val sampleSourceSet = context.configuration.sourceSets.single() + + val sampleProvider = sampleProviderFactory.build() + val sample = sampleProvider.getSample(sampleSourceSet, "org.jetbrains.dokka.sample.generator.runGenerator") + assertNotNull(sample) + + val expectedImports = listOf( + "import org.jetbrains.dokka.DokkaConfiguration", + "import org.jetbrains.dokka.DokkaGenerator", + "import org.jetbrains.dokka.utilities.DokkaLogger" + ).joinToString(separator = "\n") + + val expectedBody = "DokkaGenerator(configuration, logger).generate()" + + assertEquals(expectedImports, sample.imports) + assertEquals(expectedBody, sample.body) + } + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestData.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestData.kt new file mode 100644 index 00000000..64bfd7a3 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestData.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api + +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker + +/** + * Represents some sort of data of a [TestProject], which normally consists of a number of [TestDataFile]. + * + * This can be anything that can usually be found in a user-defined project: + * programming language source code, markdown files with documentation, samples, etc. + * + * This virtual test data will be materialized and created physically before running Dokka, + * and then passed as input files into it. + */ +@AnalysisTestDslMarker +interface TestData { + fun getFiles(): List<TestDataFile> +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestDataFile.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestDataFile.kt new file mode 100644 index 00000000..5b2233ba --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestDataFile.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api + +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker + +/** + * Represents a single file of a project's [TestData]. + * + * This file will be materialized and created physically before running Dokka, + * and then passed as one of the input files into it. + * + * @property pathFromProjectRoot this file's path from the root of the project. Must begin + * with `/` to not confuse it with relative paths. + */ +@AnalysisTestDslMarker +abstract class TestDataFile(val pathFromProjectRoot: String) { + + init { + require(pathFromProjectRoot.startsWith("/")) { + "File path going from the project's root must begin with \"/\" to not confuse it with relative paths." + } + } + + /** + * Returns the string contents of this file. + * + * The contents must be complete, as if the user themselves wrote it. For Kotlin files, + * it should return Kotlin source code (including the package and all import statements). + * For `.md` files, it should return valid Markdown documentation. + * + * These contents will be used to populate the real input file to be used by Dokka. + */ + abstract fun getContents(): String +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProject.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProject.kt new file mode 100644 index 00000000..9c0fa936 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProject.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.analysis.TestAnalysisContext +import org.jetbrains.dokka.analysis.test.api.analysis.TestAnalysisServices +import org.jetbrains.dokka.analysis.test.api.analysis.TestProjectAnalyzer +import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaConfigurationBuilder +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.util.withTempDirectory +import org.jetbrains.dokka.model.DModule + +/** + * Represents a virtual test project (as if it's user-defined) that will be used to run Dokka. + * + * A test project consists of some Dokka configuration (represented as [TestDokkaConfiguration]) + * and some project-specific data like source code and markdown files (represented as [TestData]). + * + * See [kotlinJvmTestProject], [javaTestProject] and [mixedJvmTestProject] for convenient ways + * of bootstrapping test projects. + * + * See [parse] and [useServices] functions to learn how to run Dokka with this project as input. + */ +interface TestProject { + + /** + * Verifies that this project is valid from the user's and Dokka's perspectives. + * Exists to save time with debugging difficult to catch mistakes, such as copy-pasted + * test data that is not applicable to this project. + * + * Must throw an exception if there's misconfiguration, incorrect / corrupted test data + * or API misuse. + * + * Verification is performed before running Dokka on this project. + */ + fun verify() + + /** + * Returns the configuration of this project, which will then be mapped to [DokkaConfiguration]. + * + * This is typically constructed using [BaseTestDokkaConfigurationBuilder]. + */ + fun getConfiguration(): TestDokkaConfiguration + + /** + * Returns this project's test data - a collection of source code files, markdown files + * and whatever else that can be usually found in a user-defined project. + */ + fun getTestData(): TestData +} + +/** + * Runs Dokka on the given [TestProject] and returns the generated documentable model. + * + * Can be used to verify the resulting documentable model, to check that + * everything was parsed and converted correctly. + * + * Usage example: + * ```kotlin + * val testProject = kotlinJvmTestProject { + * ... + * } + * + * val module: DModule = testProject.parse() + * ``` + */ +fun TestProject.parse(): DModule = TestProjectAnalyzer.parse(this) + +/** + * Runs Dokka on the given [TestProject] and provides not only the resulting documentable model, + * but analysis context and configuration as well, which gives you the ability to call public + * analysis services. + * + * Usage example: + * + * ```kotlin + * val testProject = kotlinJvmTestProject { + * ... + * } + * + * testProject.useServices { context -> + * val pckg: DPackage = context.module.packages.single() + * + * // use `moduleAndPackageDocumentationReader` service to get documentation of a package + * val allPackageDocs: SourceSetDependent<DocumentationNode> = moduleAndPackageDocumentationReader.read(pckg) + * } + * ``` + */ +fun TestProject.useServices(block: TestAnalysisServices.(context: TestAnalysisContext) -> Unit) { + withTempDirectory { tempDirectory -> + val (services, context) = TestProjectAnalyzer.analyze(this, tempDirectory) + services.block(context) + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProjectFactory.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProjectFactory.kt new file mode 100644 index 00000000..81a20243 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProjectFactory.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api + +import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestProject +import org.jetbrains.dokka.analysis.test.api.jvm.kotlin.KotlinJvmTestProject +import org.jetbrains.dokka.analysis.test.api.jvm.mixed.MixedJvmTestProject +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker + +/** + * Creates a single-target Kotlin/JVM test project that only has Kotlin source code. + * + * See [javaTestProject] and [mixedJvmTestProject] if you want to check interoperability + * with other JVM languages. + * + * By default, the sources are put in `/src/main/kotlin`, and the JVM version of Kotlin's + * standard library is available on classpath. + * + * See [parse] and [useServices] functions to learn how to run Dokka with this project as input. + * + * @sample org.jetbrains.dokka.analysis.test.jvm.kotlin.SampleKotlinJvmAnalysisTest.sample + */ +fun kotlinJvmTestProject(init: (@AnalysisTestDslMarker KotlinJvmTestProject).() -> Unit): TestProject { + val testData = KotlinJvmTestProject() + testData.init() + return testData +} + +/** + * Creates a Java-only test project. + * + * This can be used to test Dokka's Java support or specific + * corner cases related to parsing Java sources. + * + * By default, the sources are put in `/src/main/java`. No Kotlin source code is allowed. + * + * See [parse] and [useServices] functions to learn how to run Dokka with this project as input. + * + * @sample org.jetbrains.dokka.analysis.test.jvm.java.SampleJavaAnalysisTest.sample + */ +fun javaTestProject(init: (@AnalysisTestDslMarker JavaTestProject).() -> Unit): TestProject { + val testData = JavaTestProject() + testData.init() + return testData +} + +/** + * Creates a project where a number of JVM language sources are allowed, + * like Java and Kotlin sources co-existing in the same source directory. + * + * This can be used to test interoperability between JVM languages. + * + * By default, this project consists of a single "jvm" source set, which has two source root directories: + * * `/src/main/kotlin` + * * `/src/main/java` + * + * See [parse] and [useServices] functions to learn how to run Dokka with this project as input. + * + * @sample org.jetbrains.dokka.analysis.test.jvm.mixed.SampleMixedJvmAnalysisTest.sample + */ +fun mixedJvmTestProject(init: (@AnalysisTestDslMarker MixedJvmTestProject).() -> Unit): TestProject { + val testProject = MixedJvmTestProject() + testProject.init() + return testProject +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisContext.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisContext.kt new file mode 100644 index 00000000..de6efb1b --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisContext.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.analysis + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.TestProject +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.plugability.DokkaContext + +/** + * Context and data gathered during the analysis of a [TestProject]. + */ +class TestAnalysisContext( + + /** + * The actual [DokkaContext] that was used to run Dokka. + * + * Includes all plugins and classes available on classpath during the analysis. + */ + val context: DokkaContext, + + /** + * The actual [DokkaConfiguration] that was used to run Dokka. + * + * It was initially mapped from [TestDokkaConfiguration], and then added to by Dokka itself. + */ + val configuration: DokkaConfiguration, + + /** + * The entry point to the documentable model of the analyzed [TestProject]. + */ + val module: DModule +) diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt new file mode 100644 index 00000000..ab70bbd4 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.analysis + +import org.jetbrains.dokka.analysis.kotlin.KotlinAnalysisPlugin +import org.jetbrains.dokka.analysis.kotlin.internal.ModuleAndPackageDocumentationReader +import org.jetbrains.dokka.analysis.kotlin.internal.SampleProviderFactory + +/** + * Services exposed in [KotlinAnalysisPlugin] that are ready to be used. + * + * This class exists purely for convenience and to reduce boilerplate in tests. + * It is analogous to calling `context.plugin<KotlinAnalysisPlugin>().querySingle { serviceName }`. + */ +class TestAnalysisServices( + val sampleProviderFactory: SampleProviderFactory, + val moduleAndPackageDocumentationReader: ModuleAndPackageDocumentationReader +) diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt new file mode 100644 index 00000000..1668b53f --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt @@ -0,0 +1,223 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.analysis + +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin +import org.jetbrains.dokka.analysis.test.api.TestDataFile +import org.jetbrains.dokka.analysis.test.api.TestProject +import org.jetbrains.dokka.analysis.test.api.configuration.toDokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.parse +import org.jetbrains.dokka.analysis.test.api.useServices +import org.jetbrains.dokka.analysis.test.api.util.withTempDirectory +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.dokka.transformers.documentation.DefaultDocumentableMerger +import org.jetbrains.dokka.transformers.documentation.DocumentableMerger +import org.jetbrains.dokka.transformers.sources.SourceToDocumentableTranslator +import org.jetbrains.dokka.utilities.DokkaConsoleLogger +import org.jetbrains.dokka.utilities.LoggingLevel +import java.io.File + +/** + * The main logger used for running Dokka and analyzing projects. + * + * Changing the level to [LoggingLevel.DEBUG] can help with debugging faulty tests + * or tricky corner cases. + */ +val analysisLogger = DokkaConsoleLogger(minLevel = LoggingLevel.INFO) + +/** + * Analyzer of the test projects, it is essentially a very simple Dokka runner. + * + * Takes all virtual files of the given [TestProject], creates the real files for + * them in a temporary directory, and then runs Dokka with this temporary directory + * as the input user project. This allows us to simulate Dokka's behavior and results + * on a made-up project as if it were real and run via the CLI runner. + * + * Executes only a limited number of steps and uses a small subset of [CoreExtensions] + * that are necessary to test the analysis logic. + * + * Works only with single-module projects, where the source code of this project + * resides in the root `src` directory. Works with multiple source sets and targets, + * so both simple Kotlin/JVM and more complicated Kotlin Multiplatform project must work. + */ +object TestProjectAnalyzer { + + /** + * A quick way to analyze a [TestProject], for cases when only the documentable + * model is needed to verify the result. + * + * Creates the input test files, runs Dokka and then deletes them right after the documentable + * model has been created, leaving no trailing files or any other garbage behind. + * + * @see [TestProject.parse] for a user-friendly way to call it + */ + fun parse(testProject: TestProject): DModule { + // since we only need documentables, we can delete the input test files right away + return withTempDirectory(analysisLogger) { tempDirectory -> + val (_, context) = testProject.initialize(outputDirectory = tempDirectory) + generateDocumentableModel(context) + } + } + + /** + * Works in the same way as [parse], but it returns the context and configuration used for + * running Dokka, and does not delete the input test files at the end of the execution - it + * must be taken care of on call site. + * + * @param persistentDirectory a directory that will be used to generate the input test files into. + * It must be available during the test run, especially if services are used, + * otherwise parts of Dokka might not work as expected. Can be safely deleted + * at the end of the test after all asserts have been run. + * + * @see [TestProject.useServices] for a user-friendly way to call it + */ + fun analyze( + testProject: TestProject, + persistentDirectory: File + ): Pair<TestAnalysisServices, TestAnalysisContext> { + val (dokkaConfiguration, dokkaContext) = testProject.initialize(outputDirectory = persistentDirectory) + val analysisServices = createTestAnalysisServices(dokkaContext) + val testAnalysisContext = TestAnalysisContext( + context = dokkaContext, + configuration = dokkaConfiguration, + module = generateDocumentableModel(dokkaContext) + ) + return analysisServices to testAnalysisContext + } + + /** + * Prepares this [TestProject] for analysis by creating + * the test files, setting up context and configuration. + */ + private fun TestProject.initialize(outputDirectory: File): Pair<DokkaConfiguration, DokkaContext> { + analysisLogger.progress("Initializing and verifying project $this") + this.verify() + require(outputDirectory.isDirectory) { + "outputDirectory has to exist and be a directory: $outputDirectory" + } + this.initializeTestFiles(relativeToDir = outputDirectory) + + analysisLogger.progress("Creating configuration and context") + val testDokkaConfiguration = this.getConfiguration() + val dokkaConfiguration = testDokkaConfiguration.toDokkaConfiguration(projectDir = outputDirectory).also { + it.verify() + } + return dokkaConfiguration to createContext(dokkaConfiguration) + } + + /** + * Takes the virtual [TestDataFile] of this [TestProject] and creates + * the real files relative to the [relativeToDir] param. + */ + private fun TestProject.initializeTestFiles(relativeToDir: File) { + analysisLogger.progress("Initializing test files relative to the \"$relativeToDir\" directory") + + this.getTestData().getFiles().forEach { + val testDataFile = relativeToDir.resolve(it.pathFromProjectRoot.removePrefix("/")) + try { + testDataFile.parentFile.mkdirs() + } catch (e: Exception) { + // the IOException thrown from `mkdirs()` has no details and thus is more difficult to debug. + throw IllegalStateException("Unable to create dirs \"${testDataFile.parentFile}\"", e) + } + + analysisLogger.debug("Creating \"${testDataFile.absolutePath}\"") + check(testDataFile.createNewFile()) { + "Unable to create a test file: ${testDataFile.absolutePath}" + } + testDataFile.writeText(it.getContents(), Charsets.UTF_8) + } + } + + /** + * Verifies this [DokkaConfiguration] to make sure there are no unexpected + * parameter option values, such as non-existing classpath entries. + * + * If this method fails, it's likely there's a configuration error in the test, + * or an exception must be made in one of the checks. + */ + private fun DokkaConfiguration.verify() { + this.includes.forEach { verifyFileExists(it) } + this.sourceSets.forEach { sourceSet -> + sourceSet.classpath.forEach { verifyFileExists(it) } + sourceSet.includes.forEach { verifyFileExists(it) } + sourceSet.samples.forEach { verifyFileExists(it) } + // we do not verify sourceRoots since the source directory + // is not guaranteed to exist even if it was configured. + } + } + + private fun verifyFileExists(file: File) { + if (!file.exists() && !file.absolutePath.contains("non-existing")) { + throw IllegalArgumentException( + "The provided file does not exist. Bad test data or configuration? " + + "If it is done intentionally, add \"non-existing\" to the path or the name. File: \"$file\"" + ) + } + } + + private fun createContext(dokkaConfiguration: DokkaConfiguration): DokkaContext { + analysisLogger.progress("Creating DokkaContext from test configuration") + return DokkaContext.create( + configuration = dokkaConfiguration, + logger = analysisLogger, + pluginOverrides = listOf() + ) + } + + /** + * Generates the documentable model by using all available [SourceToDocumentableTranslator] extensions, + * and then merging all the results into a single [DModule] by calling [DocumentableMerger]. + */ + private fun generateDocumentableModel(context: DokkaContext): DModule { + analysisLogger.progress("Generating the documentable model") + val sourceSetModules = context + .configuration + .sourceSets + .map { sourceSet -> translateSources(sourceSet, context) } + .flatten() + + if (sourceSetModules.isEmpty()) { + throw IllegalStateException("Got no modules after translating sources. Is the test data set up?") + } + + return DefaultDocumentableMerger(context).invoke(sourceSetModules) + ?: error("Unable to merge documentables for some reason") + } + + /** + * Translates input source files to the documentable model by using + * all registered [SourceToDocumentableTranslator] core extensions. + */ + private fun translateSources(sourceSet: DokkaConfiguration.DokkaSourceSet, context: DokkaContext): List<DModule> { + val translators = context[CoreExtensions.sourceToDocumentableTranslator] + require(translators.isNotEmpty()) { + "Need at least one source to documentable translator to run tests, otherwise no data will be generated." + } + analysisLogger.debug("Translating sources for ${sourceSet.sourceSetID}") + return translators.map { it.invoke(sourceSet, context) } + } + + /** + * A helper function to query analysis services, to avoid + * boilerplate and misconfiguration in the tests. + * + * The idea is to provide the users with ready-to-use services, + * without them having to know how to query or configure them. + */ + private fun createTestAnalysisServices(context: DokkaContext): TestAnalysisServices { + analysisLogger.progress("Creating analysis services") + val internalPlugin = context.plugin<InternalKotlinAnalysisPlugin>() + return TestAnalysisServices( + sampleProviderFactory = internalPlugin.querySingle { sampleProviderFactory }, + moduleAndPackageDocumentationReader = internalPlugin.querySingle { moduleAndPackageDocumentationReader } + ) + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfiguration.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfiguration.kt new file mode 100644 index 00000000..5c5a0daf --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfiguration.kt @@ -0,0 +1,171 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.configuration + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaSourceSetID +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.analysis.test.api.TestProject + +/** + * Configuration options for [TestProject]. + * + * Represents a trimmed-down version of [DokkaConfiguration] that only + * exposes properties that can be used by Dokka's analysis implementations. + */ +data class TestDokkaConfiguration( + + /** + * Name of this [TestProject]. + * + * @see DokkaConfiguration.moduleName + */ + val moduleName: String, + + /** + * References Markdown files that contain documentation for this module and packages. + * + * Contains paths relative to the root of [TestProject], so it must begin with `/`. + * + * Example: `/docs/module.md` + * + * @see DokkaConfiguration.includes + * @see https://kotlinlang.org/docs/dokka-module-and-package-docs.html + */ + val includes: Set<String> = emptySet(), + + /** + * A number of source directories and their configuration options that + * make up this project. + * + * A multiplatform Kotlin project will typically have multiple source sets + * for the supported platforms, whereas a single Kotlin/JVM project will have only one. + * + * @see TestDokkaSourceSet + */ + val sourceSets: Set<TestDokkaSourceSet> + + +) { + override fun toString(): String { + return "TestDokkaConfiguration(moduleName='$moduleName', includes=$includes, sourceSets=$sourceSets)" + } +} + +/** + * Configuration options for a collection of source files for a specific platform. + * + * Represents a trimmed-down version of [DokkaConfiguration.DokkaSourceSet] that only + * exposes properties that can be used by Dokka's analysis implementations. + * + * @see https://kotlinlang.org/docs/multiplatform-discover-project.html#source-sets + */ +data class TestDokkaSourceSet ( + + /** + * @see DokkaConfiguration.DokkaSourceSet.analysisPlatform + */ + val analysisPlatform: Platform, + + /** + * A unique identifier of this source set in the scope of the project. + * + * It must be unique even if two source sets have the same name, + * the same platform or the same configuration. + * + * @see DokkaConfiguration.DokkaSourceSet.sourceSetID + */ + val sourceSetID: DokkaSourceSetID, + + /** + * A set of source set ids that this source set depends on. + * + * @see DokkaConfiguration.DokkaSourceSet.dependentSourceSets + */ + val dependentSourceSets: Set<DokkaSourceSetID>, + + /** + * A set of directories that contain the source files for this source set. + * + * A source set typically has only one source root directory, but there can be additional + * ones if the project has generated sources that reside separately, or if the `@sample` + * tag is used, and the samples reside in a different directory. + * + * Contains paths relative to the root of [TestProject], so it must begin with `/`. + * + * @see DokkaConfiguration.DokkaSourceSet.sourceRoots + */ + val sourceRoots: Set<String>, + + /** + * A set of JARs to be used for analyzing sources. + * + * If this [TestProject] exposes types from external libraries, Dokka needs to know + * about these libraries so that it can generate correct signatures and documentation. + * + * Contains absolute paths, can be any file, even if it has nothing to do with unit + * tests or test project. + * + * @see DokkaConfiguration.DokkaSourceSet.classpath + */ + val classpath: Set<String>, + + /** + * References Markdown files that contain documentation for this module and packages. + * + * Contains paths relative to the root of [TestProject], so it must begin with `/`. + * + * Example: `/docs/module.md` + * + * @see DokkaConfiguration.DokkaSourceSet.includes + * @see https://kotlinlang.org/docs/dokka-module-and-package-docs.html + */ + val includes: Set<String> = emptySet(), + + /** + * A set of Kotlin files with functions that show how to use certain API. + * + * Contains paths relative to the root of [TestProject], so it must begin with `/`. + * + * Example: `/samples/collectionSamples.kt` + * + * @see DokkaConfiguration.DokkaSourceSet.samples + */ + val samples: Set<String> = emptySet(), + + /** + * Compatibility mode for Kotlin language version X.Y. + * + * Example: `1.9` + * + * @see https://kotlinlang.org/docs/compatibility-modes.html + */ + val languageVersion: String? = null, + + /** + * Compatibility mode for Kotlin API version X.Y. + * + * Example: `1.9` + * + * @see https://kotlinlang.org/docs/compatibility-modes.html + */ + val apiVersion: String? = null, + + +) { + override fun toString(): String { + return "TestDokkaSourceSet(" + + "analysisPlatform=$analysisPlatform, " + + "sourceSetID=$sourceSetID, " + + "dependentSourceSets=$dependentSourceSets, " + + "sourceRoots=$sourceRoots, " + + "classpath=$classpath, " + + "includes=$includes, " + + "samples=$samples, " + + "languageVersion=$languageVersion, " + + "apiVersion=$apiVersion" + + ")" + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt new file mode 100644 index 00000000..b6563fb7 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt @@ -0,0 +1,145 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.configuration + +import org.jetbrains.dokka.analysis.test.api.TestProject +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker + +/** + * A builder for [TestDokkaConfiguration] that contains base options. + * + * Implementations can override the properties to modify the resulting test + * data or prohibit setting certain properties. + */ +@AnalysisTestDslMarker +abstract class BaseTestDokkaConfigurationBuilder { + + /** + * @see TestDokkaConfiguration.moduleName + */ + abstract var moduleName: String + + + /** + * @see TestDokkaConfiguration.includes + */ + open var includes: Set<String> = emptySet() + + /** + * Verifies that this source set configuration is valid. For example, + * it should check that all file paths are set in the expected format. + * + * Must be invoked manually as part of [TestProject.verify]. + */ + open fun verify() { + includes.forEach { + verifyFilePathStartsWithSlash("includes", it) + verifyFileExtension("includes", it, ".md") + } + } + + abstract fun build(): TestDokkaConfiguration + + override fun toString(): String { + return "BaseTestDokkaConfigurationBuilder(moduleName='$moduleName', includes=$includes)" + } +} + +/** + * A builder for [TestDokkaSourceSet] that contains base options. + * + * Implementations can override the properties to modify the resulting test + * data or prohibit setting certain properties. + */ +@AnalysisTestDslMarker +abstract class BaseTestDokkaSourceSetBuilder { + + /** + * Directories **additional** to the default source roots. + * + * @see TestDokkaSourceSet.sourceRoots + */ + open var additionalSourceRoots: Set<String> = emptySet() + + /** + * JARs **additional** to the default classpath. + * + * @see TestDokkaSourceSet.classpath + */ + open var additionalClasspath: Set<String> = emptySet() + + /** + * @see TestDokkaSourceSet.includes + */ + open var includes: Set<String> = emptySet() + + /** + * @see TestDokkaSourceSet.samples + */ + open var samples: Set<String> = emptySet() + + /** + * @see TestDokkaSourceSet.languageVersion + */ + open var languageVersion: String? = null + + /** + * @see TestDokkaSourceSet.apiVersion + */ + open var apiVersion: String? = null + + /** + * Verifies that this source set configuration is valid. For example, + * it should check that all file paths are set in the expected format. + * + * Must be invoked manually during the verification of the + * higher-level [BaseTestDokkaConfigurationBuilder.verify]. + */ + open fun verify() { + additionalSourceRoots.forEach { + verifyFilePathStartsWithSlash("additionalSourceRoots", it) + } + additionalClasspath.forEach { + // this check can be extended to accept .klib, .class or other files + // as the need for it arises, as long as Dokka supports it + verifyFileExtension("additionalClasspath", it, ".jar") + } + includes.forEach { + verifyFilePathStartsWithSlash("includes", it) + verifyFileExtension("includes", it, ".md") + } + samples.forEach { + verifyFilePathStartsWithSlash("samples", it) + verifyFileExtension("samples", it, ".kt") + } + } + + abstract fun build(): TestDokkaSourceSet + + override fun toString(): String { + return "BaseTestDokkaSourceSetBuilder(" + + "additionalSourceRoots=$additionalSourceRoots, " + + "additionalClasspath=$additionalClasspath, " + + "includes=$includes, " + + "samples=$samples, " + + "languageVersion=$languageVersion, " + + "apiVersion=$apiVersion" + + ")" + } +} + +internal fun verifyFilePathStartsWithSlash(propertyName: String, path: String) { + require(path.startsWith("/")) { + "Property $propertyName must contain paths relative to the root of the project. " + + "Please, prefix it with \"/\" for readability and consistency." + } +} + +internal fun verifyFileExtension(propertyName: String, filePath: String, expectedExtension: String) { + require(filePath.endsWith(expectedExtension)) { + "Property $propertyName only accepts files with \"$expectedExtension\" extension. " + + "Got: \"${filePath.substringAfterLast("/")}\"." + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationMapper.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationMapper.kt new file mode 100644 index 00000000..f6356ab2 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationMapper.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.configuration + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaSourceSetID +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.analysis.test.api.util.mapToSet +import java.io.File + +/** + * Maps [TestDokkaConfiguration] to the actual [DokkaConfiguration] that will + * be used to run Dokka; The resulting configuration must be valid from Dokka's perspective. + * + * @receiver test configuration to map to the real one; all file paths must be relative to the + * root of the project. + * @param projectDir the actual project directory that will be used to create test files in; + * the path must be absolute and must exist. + */ +fun TestDokkaConfiguration.toDokkaConfiguration(projectDir: File): DokkaConfiguration { + require(projectDir.exists() && projectDir.isDirectory) { + "Expected the \"projectDir\" File param to exist and be a directory" + } + + val moduleName = this.moduleName + val includes = this.includes.mapToSet { it.relativeTo(projectDir) } + val sourceSets = this.sourceSets.map { it.toDokkaSourceSet(projectDir) } + + return object : DokkaConfiguration { + + /* + * NOTE: The getters need to return data that can be compared by value. + * This means you can't recreate lists of interfaces on every invocation + * as their equals will return false, leading to difficult to trace bugs, + * especially when it comes to `SourceSetDependent<T>` + */ + + override val moduleName: String + get() = moduleName + + override val includes: Set<File> + get() = includes + + override val sourceSets: List<DokkaConfiguration.DokkaSourceSet> + get() = sourceSets + + /* + * The plugin API uses the properties below to initialize plugins found on classpath. + * They are not settable directly in analysis tests, but must not throw any exceptions. + */ + override val pluginsClasspath: List<File> + get() = emptyList() + override val pluginsConfiguration: List<DokkaConfiguration.PluginConfiguration> + get() = emptyList() + + /* + * The properties below are not used by the analysis modules, + * and thus they don't need to be supported. + * + * If one of the properties below starts being used during + * analysis (i.e starts throwing an exception), a corresponding + * test property should be added along with the mapping. + */ + override val moduleVersion: String + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val outputDir: File + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val cacheRoot: File + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val offlineMode: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val failOnWarning: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val modules: List<DokkaConfiguration.DokkaModuleDescription> + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val delayTemplateSubstitution: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val suppressObviousFunctions: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val suppressInheritedMembers: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val finalizeCoroutines: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + } +} + +private fun TestDokkaSourceSet.toDokkaSourceSet(relativeToDir: File): DokkaConfiguration.DokkaSourceSet { + val analysisPlatform = this.analysisPlatform + val sourceSetID = this.sourceSetID + val dependentSourceSets = this.dependentSourceSets + val sourceRoots = this.sourceRoots.mapToSet { it.relativeTo(relativeToDir) } + val classpath = this.classpath.map { File(it) } + val includes = this.includes.mapToSet { it.relativeTo(relativeToDir) } + val samples = this.samples.mapToSet { it.relativeTo(relativeToDir) } + val languageVersion = this.languageVersion + val apiVersion = this.apiVersion + + return object : DokkaConfiguration.DokkaSourceSet { + + /* + * NOTE: The getters need to return data that can be compared by value. + * This means you can't recreate lists of interfaces on every invocation + * as their equals will return false, leading to difficult to trace bugs, + * especially when it comes to `SourceSetDependent<T>` + */ + + override val analysisPlatform: Platform + get() = analysisPlatform + + override val sourceSetID: DokkaSourceSetID + get() = sourceSetID + + override val dependentSourceSets: Set<DokkaSourceSetID> + get() = dependentSourceSets + + override val sourceRoots: Set<File> + get() = sourceRoots + + override val classpath: List<File> + get() = classpath + + override val includes: Set<File> + get() = includes + + override val samples: Set<File> + get() = samples + + override val languageVersion: String? + get() = languageVersion + + override val apiVersion: String? + get() = apiVersion + + + /* + * The properties below are not used by the analysis modules, + * and thus they don't need to be supported. + * + * If one of the properties below starts being used during + * analysis (i.e starts throwing an exception), a corresponding + * test property should be added along with the mapping. + */ + override val displayName: String + get() = throw NotImplementedError("Not expected to be used by analysis modules") + @Suppress("OVERRIDE_DEPRECATION") + override val includeNonPublic: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val reportUndocumented: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val skipEmptyPackages: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val skipDeprecated: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val jdkVersion: Int + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val sourceLinks: Set<DokkaConfiguration.SourceLinkDefinition> + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val perPackageOptions: List<DokkaConfiguration.PackageOptions> + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val externalDocumentationLinks: Set<DokkaConfiguration.ExternalDocumentationLink> + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val noStdlibLink: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val noJdkLink: Boolean + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val suppressedFiles: Set<File> + get() = throw NotImplementedError("Not expected to be used by analysis modules") + override val documentedVisibilities: Set<DokkaConfiguration.Visibility> + get() = throw NotImplementedError("Not expected to be used by analysis modules") + } +} + +private fun String.relativeTo(dir: File): File { + return dir.resolve(this.removePrefix("/")) +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaConfigurationBuilder.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaConfigurationBuilder.kt new file mode 100644 index 00000000..6775fa21 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaConfigurationBuilder.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.java + +import org.jetbrains.dokka.DokkaSourceSetID +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaConfigurationBuilder +import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaSourceSetBuilder +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaSourceSet +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker + +/** + * An implementation of [BaseTestDokkaConfigurationBuilder] specific to [JavaTestProject]. + */ +class JavaTestConfigurationBuilder : BaseTestDokkaConfigurationBuilder() { + override var moduleName: String = "javaTestProject" + + private val javaSourceSetBuilder = JavaTestSourceSetBuilder() + + @AnalysisTestDslMarker + fun javaSourceSet(fillSourceSet: JavaTestSourceSetBuilder.() -> Unit) { + fillSourceSet(javaSourceSetBuilder) + } + + override fun verify() { + super.verify() + javaSourceSetBuilder.verify() + } + + override fun build(): TestDokkaConfiguration { + return TestDokkaConfiguration( + moduleName = moduleName, + includes = includes, + sourceSets = setOf(javaSourceSetBuilder.build()) + ) + } +} + +/** + * An implementation of [BaseTestDokkaSourceSetBuilder] specific to [JavaTestProject]. + * + * Defines sensible defaults that should cover the majority of simple projects. + */ +class JavaTestSourceSetBuilder : BaseTestDokkaSourceSetBuilder() { + override fun build(): TestDokkaSourceSet { + return TestDokkaSourceSet( + analysisPlatform = Platform.jvm, + sourceSetID = DokkaSourceSetID(scopeId = "project", sourceSetName = "java"), + dependentSourceSets = setOf(), + sourceRoots = additionalSourceRoots + setOf(JavaTestProject.DEFAULT_SOURCE_ROOT), + classpath = additionalClasspath, // TODO [beresnev] is kotlin jvm stdlib needed here? + includes = includes, + samples = samples, + languageVersion = languageVersion, + apiVersion = apiVersion + ) + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaFileCreator.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaFileCreator.kt new file mode 100644 index 00000000..2a79210f --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaFileCreator.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.java + +/** + * Declares a capability that a `.java` file can be created in the scope of the implementation. + */ +interface JavaFileCreator { + + /** + * Creates a `.java` file. + * + * By default, the package of this file is deduced automatically from the [pathFromSrc] param. + * For example, for a path `org/jetbrains/dokka/test` the package will be `org.jetbrains.dokka.test`. + * It is normally prohibited for Java files to have a mismatch in package and file path, so it + * cannot be overridden. + * + * @param pathFromSrc path relative to the source code directory of the project. + * Must contain packages (if any) and end in `.java`. + * Example: `org/jetbrains/dokka/test/MyClass.java` + */ + fun javaFile(pathFromSrc: String, fillFile: JavaTestDataFile.() -> Unit) +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestData.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestData.kt new file mode 100644 index 00000000..48705e78 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestData.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.java + +import org.jetbrains.dokka.analysis.test.api.TestData +import org.jetbrains.dokka.analysis.test.api.TestDataFile +import org.jetbrains.dokka.analysis.test.api.util.filePathToPackageName + +/** + * A container for populating and holding Java source code test data. + * + * This container exists so that common creation, population and verification logic + * can be reused, instead of having to implement [JavaFileCreator] multiple times. + * + * @param pathToJavaSources path to the `src` directory in which Java sources must reside. + * Must be relative to the root of the test project. Example: `/src/main/java` + * @see TestData + */ +class JavaTestData( + private val pathToJavaSources: String +) : TestData, JavaFileCreator { + + private val files = mutableListOf<TestDataFile>() + + override fun javaFile(pathFromSrc: String, fillFile: JavaTestDataFile.() -> Unit) { + val fileName = pathFromSrc.substringAfterLast("/") + check(fileName.endsWith(".java")) { "Java files are expected to have .java extension" } + + val testDataFile = JavaTestDataFile( + fullyQualifiedPackageName = filePathToPackageName(pathFromSrc), + pathFromProjectRoot = "$pathToJavaSources/$pathFromSrc", + ) + fillFile(testDataFile) + testDataFile.checkFileNameIsPresent(fileName) + files.add(testDataFile) + } + + private fun JavaTestDataFile.checkFileNameIsPresent(fileName: String) { + check(this.getContents().contains(fileName.removeSuffix(".java"))) { + "Expected the .java file name to be the same as the top-level declaration name (class/interface)." + } + } + + override fun getFiles(): List<TestDataFile> { + return files + } + + override fun toString(): String { + return "JavaTestData(pathToJavaSources='$pathToJavaSources', files=$files)" + } +} + diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestDataFile.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestDataFile.kt new file mode 100644 index 00000000..d8cc86a8 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestDataFile.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.java + +import org.jetbrains.dokka.analysis.test.api.TestDataFile + +class JavaTestDataFile( + fullyQualifiedPackageName: String, + pathFromProjectRoot: String, +) : TestDataFile(pathFromProjectRoot = pathFromProjectRoot) { + + private var fileContents = "package $fullyQualifiedPackageName;" + System.lineSeparator().repeat(2) + + operator fun String.unaryPlus() { + fileContents += (this.trimIndent() + System.lineSeparator()) + } + + override fun getContents(): String { + return fileContents + } + + override fun toString(): String { + return "JavaTestDataFile(pathFromProjectRoot='$pathFromProjectRoot', fileContents='$fileContents')" + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestProject.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestProject.kt new file mode 100644 index 00000000..39f0f0f6 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestProject.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.java + +import org.jetbrains.dokka.analysis.test.api.TestData +import org.jetbrains.dokka.analysis.test.api.TestDataFile +import org.jetbrains.dokka.analysis.test.api.TestProject +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.javaTestProject +import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestData +import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestDataFile +import org.jetbrains.dokka.analysis.test.api.markdown.MdFileCreator +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker +import org.jetbrains.dokka.analysis.test.api.util.flatListOf + +/** + * @see javaTestProject for an explanation and a convenient way to construct this project + */ +class JavaTestProject : TestProject, JavaFileCreator, MdFileCreator { + + private val projectConfigurationBuilder = JavaTestConfigurationBuilder() + + private val javaSourceSet = JavaTestData(pathToJavaSources = DEFAULT_SOURCE_ROOT) + private val markdownTestData = MarkdownTestData() + + @AnalysisTestDslMarker + fun dokkaConfiguration(fillConfiguration: JavaTestConfigurationBuilder.() -> Unit) { + fillConfiguration(projectConfigurationBuilder) + } + + @AnalysisTestDslMarker + override fun javaFile(pathFromSrc: String, fillFile: JavaTestDataFile.() -> Unit) { + javaSourceSet.javaFile(pathFromSrc, fillFile) + } + + @AnalysisTestDslMarker + override fun mdFile(pathFromProjectRoot: String, fillFile: MarkdownTestDataFile.() -> Unit) { + markdownTestData.mdFile(pathFromProjectRoot, fillFile) + } + + override fun verify() { + projectConfigurationBuilder.verify() + } + + override fun getConfiguration(): TestDokkaConfiguration { + return projectConfigurationBuilder.build() + } + + override fun getTestData(): TestData { + return object : TestData { + override fun getFiles(): List<TestDataFile> { + return flatListOf( + this@JavaTestProject.javaSourceSet.getFiles(), + this@JavaTestProject.markdownTestData.getFiles() + ) + } + } + } + + override fun toString(): String { + return "JavaTestProject(" + + "projectConfigurationBuilder=$projectConfigurationBuilder, " + + "javaSourceSet=$javaSourceSet, " + + "markdownTestData=$markdownTestData" + + ")" + } + + internal companion object { + internal const val DEFAULT_SOURCE_ROOT = "/src/main/java" + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmConfigurationBuilder.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmConfigurationBuilder.kt new file mode 100644 index 00000000..a0255611 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmConfigurationBuilder.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.kotlin + +import org.jetbrains.dokka.DokkaSourceSetID +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaConfigurationBuilder +import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaSourceSetBuilder +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaSourceSet +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker + +/** + * An implementation of [BaseTestDokkaConfigurationBuilder] specific to [KotlinJvmTestProject]. + */ +class KotlinJvmTestConfigurationBuilder : BaseTestDokkaConfigurationBuilder() { + override var moduleName: String = "kotlinJvmTestProject" + + private val kotlinSourceSetBuilder = KotlinJvmTestSourceSetBuilder() + + @AnalysisTestDslMarker + fun kotlinSourceSet(fillSourceSet: KotlinJvmTestSourceSetBuilder.() -> Unit) { + fillSourceSet(kotlinSourceSetBuilder) + } + + override fun verify() { + super.verify() + kotlinSourceSetBuilder.verify() + } + + override fun build(): TestDokkaConfiguration { + return TestDokkaConfiguration( + moduleName = moduleName, + includes = includes, + sourceSets = setOf(kotlinSourceSetBuilder.build()) + ) + } +} + +class KotlinJvmTestSourceSetBuilder : BaseTestDokkaSourceSetBuilder() { + override fun build(): TestDokkaSourceSet { + return TestDokkaSourceSet( + analysisPlatform = Platform.jvm, + sourceSetID = DokkaSourceSetID(scopeId = "project", sourceSetName = "kotlin"), + dependentSourceSets = setOf(), + sourceRoots = additionalSourceRoots + setOf(KotlinJvmTestProject.DEFAULT_SOURCE_ROOT), + classpath = additionalClasspath + setOf(getKotlinJvmStdlibJarPath()), + includes = includes, + samples = samples, + languageVersion = languageVersion, + apiVersion = apiVersion + ) + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmDependencyUtils.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmDependencyUtils.kt new file mode 100644 index 00000000..c90e126f --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmDependencyUtils.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.kotlin + +private val lazyKotlinJvmStdlibJar by lazy { + ClassLoader.getSystemResource("kotlin/jvm/Strictfp.class") + ?.file + ?.replace("file:", "") + ?.replaceAfter(".jar", "") + ?: error("Unable to find Kotlin's JVM stdlib jar.") +} + +/** + * Returns an absolute path to the JAR file of Kotlin's standard library for the JVM platform. + * + * Example: `~/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.9.10/xx/kotlin-stdlib-1.9.10.jar` + */ +internal fun getKotlinJvmStdlibJarPath(): String { + return lazyKotlinJvmStdlibJar +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmTestProject.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmTestProject.kt new file mode 100644 index 00000000..178a1dc3 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmTestProject.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.kotlin + +import org.jetbrains.dokka.analysis.test.api.TestData +import org.jetbrains.dokka.analysis.test.api.TestDataFile +import org.jetbrains.dokka.analysis.test.api.TestProject +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.kotlin.KotlinTestData +import org.jetbrains.dokka.analysis.test.api.kotlin.KotlinTestDataFile +import org.jetbrains.dokka.analysis.test.api.kotlin.KtFileCreator +import org.jetbrains.dokka.analysis.test.api.kotlin.sample.KotlinSampleFileCreator +import org.jetbrains.dokka.analysis.test.api.kotlin.sample.KotlinSampleTestData +import org.jetbrains.dokka.analysis.test.api.kotlin.sample.KotlinSampleTestDataFile +import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject +import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestData +import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestDataFile +import org.jetbrains.dokka.analysis.test.api.markdown.MdFileCreator +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker +import org.jetbrains.dokka.analysis.test.api.util.flatListOf + +/** + * @see kotlinJvmTestProject for an explanation and a convenient way to construct this project + */ +class KotlinJvmTestProject : TestProject, KtFileCreator, MdFileCreator, KotlinSampleFileCreator { + + private val projectConfigurationBuilder = KotlinJvmTestConfigurationBuilder() + + private val kotlinSourceSet = KotlinTestData(pathToKotlinSources = DEFAULT_SOURCE_ROOT) + private val markdownTestData = MarkdownTestData() + private val kotlinSampleTestData = KotlinSampleTestData() + + @AnalysisTestDslMarker + fun dokkaConfiguration(fillConfiguration: KotlinJvmTestConfigurationBuilder.() -> Unit) { + fillConfiguration(projectConfigurationBuilder) + } + + @AnalysisTestDslMarker + override fun ktFile(pathFromSrc: String, fqPackageName: String, fillFile: KotlinTestDataFile.() -> Unit) { + kotlinSourceSet.ktFile(pathFromSrc, fqPackageName, fillFile) + } + + @AnalysisTestDslMarker + override fun mdFile(pathFromProjectRoot: String, fillFile: MarkdownTestDataFile.() -> Unit) { + markdownTestData.mdFile(pathFromProjectRoot, fillFile) + } + + @AnalysisTestDslMarker + override fun sampleFile( + pathFromProjectRoot: String, + fqPackageName: String, + fillFile: KotlinSampleTestDataFile.() -> Unit + ) { + kotlinSampleTestData.sampleFile(pathFromProjectRoot, fqPackageName, fillFile) + } + + override fun verify() { + projectConfigurationBuilder.verify() + } + + override fun getConfiguration(): TestDokkaConfiguration { + return projectConfigurationBuilder.build() + } + + override fun getTestData(): TestData { + return object : TestData { + override fun getFiles(): List<TestDataFile> { + return flatListOf( + this@KotlinJvmTestProject.kotlinSourceSet.getFiles(), + this@KotlinJvmTestProject.markdownTestData.getFiles(), + this@KotlinJvmTestProject.kotlinSampleTestData.getFiles() + ) + } + } + } + + override fun toString(): String { + return "KotlinJvmTestProject(" + + "projectConfigurationBuilder=$projectConfigurationBuilder, " + + "kotlinSourceSet=$kotlinSourceSet, " + + "markdownTestData=$markdownTestData, " + + "kotlinSampleTestData=$kotlinSampleTestData" + + ")" + } + + internal companion object { + internal const val DEFAULT_SOURCE_ROOT = "/src/main/kotlin" + } +} + + diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmConfigurationBuilder.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmConfigurationBuilder.kt new file mode 100644 index 00000000..16af6e7d --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmConfigurationBuilder.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.mixed + +import org.jetbrains.dokka.DokkaSourceSetID +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaConfigurationBuilder +import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaSourceSetBuilder +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaSourceSet +import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestProject +import org.jetbrains.dokka.analysis.test.api.jvm.kotlin.KotlinJvmTestProject +import org.jetbrains.dokka.analysis.test.api.jvm.kotlin.getKotlinJvmStdlibJarPath +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker + +/** + * An implementation of [BaseTestDokkaConfigurationBuilder] specific to [MixedJvmTestProject]. + */ +class MixedJvmTestConfigurationBuilder : BaseTestDokkaConfigurationBuilder() { + override var moduleName: String = "mixedJvmTestProject" + + private val mixedJvmSourceSetBuilder = MixedJvmTestSourceSetBuilder() + + @AnalysisTestDslMarker + fun jvmSourceSet(fillSourceDir: MixedJvmTestSourceSetBuilder.() -> Unit) { + fillSourceDir(mixedJvmSourceSetBuilder) + } + + override fun verify() { + super.verify() + mixedJvmSourceSetBuilder.verify() + } + + override fun build(): TestDokkaConfiguration { + return TestDokkaConfiguration( + moduleName = moduleName, + includes = includes, + sourceSets = setOf(mixedJvmSourceSetBuilder.build()) + ) + } + + override fun toString(): String { + return "MixedJvmTestConfigurationBuilder(" + + "moduleName='$moduleName', " + + "jvmSourceSetBuilder=$mixedJvmSourceSetBuilder" + + ")" + } +} + +class MixedJvmTestSourceSetBuilder : BaseTestDokkaSourceSetBuilder() { + override fun build(): TestDokkaSourceSet { + return TestDokkaSourceSet( + analysisPlatform = Platform.jvm, + sourceSetID = DokkaSourceSetID(scopeId = "project", sourceSetName = "jvm"), + dependentSourceSets = setOf(), + sourceRoots = additionalSourceRoots + setOf( + KotlinJvmTestProject.DEFAULT_SOURCE_ROOT, + JavaTestProject.DEFAULT_SOURCE_ROOT + ), + classpath = additionalClasspath + setOf(getKotlinJvmStdlibJarPath()), + includes = includes, + samples = samples, + languageVersion = languageVersion, + apiVersion = apiVersion + ) + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestData.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestData.kt new file mode 100644 index 00000000..24889fe2 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestData.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.mixed + +import org.jetbrains.dokka.analysis.test.api.TestData +import org.jetbrains.dokka.analysis.test.api.TestDataFile +import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaFileCreator +import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestData +import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestDataFile +import org.jetbrains.dokka.analysis.test.api.kotlin.KotlinTestData +import org.jetbrains.dokka.analysis.test.api.kotlin.KotlinTestDataFile +import org.jetbrains.dokka.analysis.test.api.kotlin.KtFileCreator +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker + +/** + * A container for populating and holding Kotlin and Java source code test data. + * + * This container exists so that common creation, population and verification logic + * can be reused, instead of having to implement [KtFileCreator] and [JavaFileCreator] multiple times. + * + * @see TestData + */ +class MixedJvmTestData(pathToSources: String) : TestData, KtFileCreator, JavaFileCreator { + + private val javaTestData = JavaTestData(pathToJavaSources = pathToSources) + private val kotlinJvmTestData = KotlinTestData(pathToKotlinSources = pathToSources) + + @AnalysisTestDslMarker + override fun javaFile(pathFromSrc: String, fillFile: JavaTestDataFile.() -> Unit) { + javaTestData.javaFile(pathFromSrc, fillFile) + } + + @AnalysisTestDslMarker + override fun ktFile(pathFromSrc: String, fqPackageName: String, fillFile: KotlinTestDataFile.() -> Unit) { + kotlinJvmTestData.ktFile(pathFromSrc, fqPackageName, fillFile) + } + + override fun getFiles(): List<TestDataFile> { + return javaTestData.getFiles() + kotlinJvmTestData.getFiles() + } + + override fun toString(): String { + return "MixedJvmTestData(javaTestData=$javaTestData, kotlinJvmTestData=$kotlinJvmTestData)" + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestProject.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestProject.kt new file mode 100644 index 00000000..32b7ce0a --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestProject.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.jvm.mixed + +import org.jetbrains.dokka.analysis.test.api.TestData +import org.jetbrains.dokka.analysis.test.api.TestDataFile +import org.jetbrains.dokka.analysis.test.api.TestProject +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestProject +import org.jetbrains.dokka.analysis.test.api.jvm.kotlin.KotlinJvmTestProject +import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestData +import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestDataFile +import org.jetbrains.dokka.analysis.test.api.markdown.MdFileCreator +import org.jetbrains.dokka.analysis.test.api.mixedJvmTestProject +import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker +import org.jetbrains.dokka.analysis.test.api.util.flatListOf + +/** + * @see mixedJvmTestProject for an explanation and a convenient way to construct this project + */ +class MixedJvmTestProject : TestProject, MdFileCreator { + + private val projectConfigurationBuilder = MixedJvmTestConfigurationBuilder() + + private val kotlinSourceDirectory = MixedJvmTestData(pathToSources = KotlinJvmTestProject.DEFAULT_SOURCE_ROOT) + private val javaSourceDirectory = MixedJvmTestData(pathToSources = JavaTestProject.DEFAULT_SOURCE_ROOT) + private val markdownTestData = MarkdownTestData() + + @AnalysisTestDslMarker + fun dokkaConfiguration(fillConfiguration: MixedJvmTestConfigurationBuilder.() -> Unit) { + fillConfiguration(projectConfigurationBuilder) + } + + @AnalysisTestDslMarker + fun kotlinSourceDirectory(fillTestData: MixedJvmTestData.() -> Unit) { + fillTestData(kotlinSourceDirectory) + } + + @AnalysisTestDslMarker + fun javaSourceDirectory(fillTestData: MixedJvmTestData.() -> Unit) { + fillTestData(javaSourceDirectory) + } + + @AnalysisTestDslMarker + override fun mdFile(pathFromProjectRoot: String, fillFile: MarkdownTestDataFile.() -> Unit) { + markdownTestData.mdFile(pathFromProjectRoot, fillFile) + } + + override fun verify() { + projectConfigurationBuilder.verify() + } + + override fun getConfiguration(): TestDokkaConfiguration { + return projectConfigurationBuilder.build() + } + + override fun getTestData(): TestData { + return object : TestData { + override fun getFiles(): List<TestDataFile> { + return flatListOf( + this@MixedJvmTestProject.kotlinSourceDirectory.getFiles(), + this@MixedJvmTestProject.javaSourceDirectory.getFiles(), + this@MixedJvmTestProject.markdownTestData.getFiles() + ) + } + } + } + + override fun toString(): String { + return "MixedJvmTestProject(" + + "projectConfigurationBuilder=$projectConfigurationBuilder, " + + "kotlinSourceDirectory=$kotlinSourceDirectory, " + + "javaSourceDirectory=$javaSourceDirectory, " + + "markdownTestData=$markdownTestData" + + ")" + } +} + diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestData.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestData.kt new file mode 100644 index 00000000..02911235 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestData.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.kotlin + +import org.jetbrains.dokka.analysis.test.api.TestData +import org.jetbrains.dokka.analysis.test.api.TestDataFile + +/** + * A container for populating and holding Kotlin source code test data. + * + * This container exists so that common creation, population and verification logic + * can be reused, instead of having to implement [KtFileCreator] multiple times. + * + * @param pathToKotlinSources path to the `src` directory in which Kotlin sources must reside. + * Must be relative to the root of the test project. Example: `/src/main/kotlin` + * @see TestData + */ +class KotlinTestData( + private val pathToKotlinSources: String +) : TestData, KtFileCreator { + + private val files = mutableListOf<TestDataFile>() + + override fun ktFile( + pathFromSrc: String, + fqPackageName: String, + fillFile: KotlinTestDataFile.() -> Unit + ) { + check(pathFromSrc.endsWith(".kt")) { "Kotlin files are expected to have .kt extension" } + + val testDataFile = KotlinTestDataFile( + pathFromProjectRoot = "$pathToKotlinSources/$pathFromSrc", + fqPackageName = fqPackageName, + ) + fillFile(testDataFile) + files.add(testDataFile) + } + + override fun getFiles(): List<TestDataFile> { + return files + } + + override fun toString(): String { + return "KotlinTestData(pathToKotlinSources='$pathToKotlinSources', files=$files)" + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestDataFile.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestDataFile.kt new file mode 100644 index 00000000..355440e7 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestDataFile.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.kotlin + +import org.jetbrains.dokka.analysis.test.api.TestDataFile + +class KotlinTestDataFile( + pathFromProjectRoot: String, + fqPackageName: String, +) : TestDataFile(pathFromProjectRoot = pathFromProjectRoot) { + + private var fileContents = "package $fqPackageName" + System.lineSeparator().repeat(2) + + operator fun String.unaryPlus() { + fileContents += (this.trimIndent() + System.lineSeparator()) + } + + override fun getContents(): String { + return fileContents + } + + override fun toString(): String { + return "KotlinTestDataFile(pathFromProjectRoot='$pathFromProjectRoot', fileContents='$fileContents')" + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KtFileCreator.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KtFileCreator.kt new file mode 100644 index 00000000..15ce31ce --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KtFileCreator.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.kotlin + +import org.jetbrains.dokka.analysis.test.api.util.filePathToPackageName + +/** + * Declares a capability that a `.kt` file can be created in the scope of the implementation. + */ +interface KtFileCreator { + + /** + * Creates a `.kt` file. + * + * By default, the package of this file is deduced automatically from the [pathFromSrc] param. + * For example, for a path `org/jetbrains/dokka/test` the package will be `org.jetbrains.dokka.test`. + * The package can be overridden by setting [fqPackageName]. + * + * @param pathFromSrc path relative to the source code directory of the project. + * Must contain packages (if any) and end in `.kt`. + * Example: `org/jetbrains/dokka/test/File.kt` + * @param fqPackageName package name to be used in the `package` statement of this file. + * This value overrides the automatically deduced package. + */ + fun ktFile( + pathFromSrc: String, + fqPackageName: String = filePathToPackageName(pathFromSrc), + fillFile: KotlinTestDataFile.() -> Unit + ) +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleFileCreator.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleFileCreator.kt new file mode 100644 index 00000000..7fa1d28d --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleFileCreator.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.kotlin.sample + +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaSourceSet + +/** + * Declares a capability that a `.kt` file can be created that will be used as a usage sample. + */ +interface KotlinSampleFileCreator { + + /** + * Creates a `.kt` file outside of the source code directory. It should be used as input + * for the `@sample` KDoc tag. + * + * To be picked by Dokka, this file must be included in [TestDokkaSourceSet.samples]. + * + * @param pathFromProjectRoot path relative to the root of the test project. Must begin + * with `/` to not confuse it with relative paths. Example: `/samples/collections.kt` + * @param fqPackageName fully qualified package name to be used in the `package` statement of the file. + * This parameter must be set because the package name cannot be deduced + * from the file path, as samples usually reside outside of the source code directory. + * Example: `org.jetbrains.dokka.sample.collections` + */ + fun sampleFile( + pathFromProjectRoot: String, + fqPackageName: String, + fillFile: KotlinSampleTestDataFile.() -> Unit + ) +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestData.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestData.kt new file mode 100644 index 00000000..9ba4187c --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestData.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.kotlin.sample + +import org.jetbrains.dokka.analysis.test.api.TestData +import org.jetbrains.dokka.analysis.test.api.TestDataFile + +/** + * A container for populating and holding Kotlin sample test data. + * + * This container exists so that common creation, population and verification logic + * can be reused, instead of having to implement [KotlinSampleFileCreator] multiple times. + * + * @see TestData + */ +class KotlinSampleTestData : TestData, KotlinSampleFileCreator { + + private val files = mutableListOf<TestDataFile>() + + override fun sampleFile( + pathFromProjectRoot: String, + fqPackageName: String, + fillFile: KotlinSampleTestDataFile.() -> Unit + ) { + check(pathFromProjectRoot.endsWith(".kt")) { "Kotlin sample files are expected to have .kt extension" } + + val testDataFile = KotlinSampleTestDataFile( + pathFromProjectRoot = pathFromProjectRoot, + fqPackageName = fqPackageName + ) + fillFile(testDataFile) + files.add(testDataFile) + } + + override fun getFiles(): List<TestDataFile> { + return files + } + + override fun toString(): String { + return "KotlinSampleTestData(files=$files)" + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestDataFile.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestDataFile.kt new file mode 100644 index 00000000..6dc8230b --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestDataFile.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.kotlin.sample + +import org.jetbrains.dokka.analysis.test.api.TestDataFile + +class KotlinSampleTestDataFile( + pathFromProjectRoot: String, + fqPackageName: String, +) : TestDataFile(pathFromProjectRoot = pathFromProjectRoot) { + + private var fileContents = "package $fqPackageName" + System.lineSeparator().repeat(2) + + operator fun String.unaryPlus() { + fileContents += (this.trimIndent() + System.lineSeparator()) + } + + override fun getContents(): String { + return fileContents + } + + override fun toString(): String { + return "KotlinSampleTestDataFile(pathFromProjectRoot='$pathFromProjectRoot', fileContents='$fileContents')" + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestData.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestData.kt new file mode 100644 index 00000000..d0cb64fb --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestData.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.markdown + +import org.jetbrains.dokka.analysis.test.api.TestData +import org.jetbrains.dokka.analysis.test.api.TestDataFile + +/** + * A container for populating and holding Markdown test data. + * + * This container exists so that common creation, population and verification logic + * can be reused, instead of having to implement [MdFileCreator] multiple times. + * + * @see TestData + */ +class MarkdownTestData : TestData, MdFileCreator { + + private val files = mutableListOf<TestDataFile>() + + override fun mdFile( + pathFromProjectRoot: String, + fillFile: MarkdownTestDataFile.() -> Unit + ) { + check(pathFromProjectRoot.endsWith(".md")) { "Markdown files are expected to have .md extension" } + + val testDataFile = MarkdownTestDataFile(pathFromProjectRoot) + fillFile(testDataFile) + files.add(testDataFile) + } + + override fun getFiles(): List<TestDataFile> { + return files + } + + override fun toString(): String { + return "MarkdownTestData(files=$files)" + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestDataFile.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestDataFile.kt new file mode 100644 index 00000000..9bb8169a --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestDataFile.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.markdown + +import org.jetbrains.dokka.analysis.test.api.TestDataFile + +class MarkdownTestDataFile( + pathFromProjectRoot: String +) : TestDataFile(pathFromProjectRoot = pathFromProjectRoot) { + + private var fileContents = "" + + operator fun String.unaryPlus() { + fileContents += (this.trimIndent() + System.lineSeparator()) + } + + override fun getContents(): String { + return fileContents + } + + override fun toString(): String { + return "MarkdownTestDataFile(pathFromProjectRoot='$pathFromProjectRoot', fileContents='$fileContents')" + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MdFileCreator.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MdFileCreator.kt new file mode 100644 index 00000000..8ba97b17 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MdFileCreator.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.markdown + +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration +import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaSourceSet + +/** + * Declares a capability that an `.md` file can be created in the scope of the implementation. + */ +interface MdFileCreator { + + /** + * Creates an `.md` (Markdown) file. + * + * If you want to use this file for module and package documentation, it must be included + * in [TestDokkaConfiguration.includes] or [TestDokkaSourceSet.includes]. + * + * @param pathFromProjectRoot path relative to the root of the test project. Must begin + * with `/` to not confuse it with relative paths. Example: `/docs/core-package.md` + */ + fun mdFile( + pathFromProjectRoot: String, + fillFile: MarkdownTestDataFile.() -> Unit + ) +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/CollectionUtils.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/CollectionUtils.kt new file mode 100644 index 00000000..6cd41594 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/CollectionUtils.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.util + +internal inline fun <reified T> flatListOf(vararg lists: List<T>): List<T> { + val overallSize = lists.sumBy { it.size } + val aggregatingList = ArrayList<T>(overallSize) + lists.forEach { + aggregatingList.addAll(it) + } + return aggregatingList +} + +internal inline fun <T, R> Iterable<T>.mapToSet(transform: (T) -> R): Set<R> { + return this.map(transform).toSet() +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DslApiUtils.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DslApiUtils.kt new file mode 100644 index 00000000..e855bb4b --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DslApiUtils.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.util + +@DslMarker +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) +annotation class AnalysisTestDslMarker diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt new file mode 100644 index 00000000..779add8d --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.api.util + +import org.jetbrains.dokka.utilities.DokkaLogger +import java.io.File +import java.io.IOException + +/** + * Converts a file path (like `org/jetbrains/dokka/test/File.kt`) to a fully qualified + * package name (like `org.jetbrains.dokka.test`). + * + * @param srcRelativeFilePath path relative to the source code directory. If the full path of the file + * is `src/main/kotlin/org/jetbrains/dokka/test/File.kt`, then only + * `org/jetbrains/dokka/test/File.kt` is expected to be passed here. + * @return fully qualified package name or an empty string in case of the root package. + */ +internal fun filePathToPackageName(srcRelativeFilePath: String): String { + return srcRelativeFilePath + .substringBeforeLast("/", missingDelimiterValue = "") + .replace("/", ".") +} + +/** + * @throws IOException if the requested temporary directory could not be created or deleted once used. + */ +internal fun <T> withTempDirectory(logger: DokkaLogger? = null, block: (tempDirectory: File) -> T): T { + @Suppress("DEPRECATION") // TODO migrate to kotlin.io.path.createTempDirectory with languageVersion >= 1.5 + val tempDir = createTempDir() + try { + logger?.debug("Created temporary directory $tempDir") + return block(tempDir) + } finally { + if (!tempDir.deleteRecursively()) { + throw IOException("Unable to delete temporary directory $tempDir") + } + logger?.debug("Deleted temporary directory $tempDir") + } +} |