diff options
Diffstat (limited to 'dokka-subprojects/analysis-kotlin-api')
18 files changed, 807 insertions, 116 deletions
diff --git a/dokka-subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api b/dokka-subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api index c65dfe5a..3b546932 100644 --- a/dokka-subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api +++ b/dokka-subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api @@ -1,5 +1,6 @@ public final class org/jetbrains/dokka/analysis/kotlin/KotlinAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin { public fun <init> ()V + public final fun getSampleAnalysisEnvironmentCreator ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; } public final class org/jetbrains/dokka/analysis/kotlin/internal/DocumentableLanguage : java/lang/Enum { @@ -51,7 +52,6 @@ public final class org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAn public final fun getInheritanceBuilder ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; public final fun getKotlinToJavaService ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; public final fun getModuleAndPackageDocumentationReader ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; - public final fun getSampleProviderFactory ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; public final fun getSyntheticDocumentableDetector ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; } @@ -65,21 +65,24 @@ public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/Mod public abstract fun read (Lorg/jetbrains/dokka/model/DPackage;)Ljava/util/Map; } -public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider : java/lang/AutoCloseable { - public abstract fun getSample (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Ljava/lang/String;)Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider$SampleSnippet; +public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/SyntheticDocumentableDetector { + public abstract fun isSynthetic (Lorg/jetbrains/dokka/model/Documentable;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Z } -public final class org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider$SampleSnippet { - public fun <init> (Ljava/lang/String;Ljava/lang/String;)V - public final fun getBody ()Ljava/lang/String; - public final fun getImports ()Ljava/lang/String; +public abstract interface class org/jetbrains/dokka/analysis/kotlin/sample/SampleAnalysisEnvironment { + public abstract fun resolveSample (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Ljava/lang/String;)Lorg/jetbrains/dokka/analysis/kotlin/sample/SampleSnippet; } -public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/SampleProviderFactory { - public abstract fun build ()Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider; +public abstract interface class org/jetbrains/dokka/analysis/kotlin/sample/SampleAnalysisEnvironmentCreator { + public abstract fun use (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } -public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/SyntheticDocumentableDetector { - public abstract fun isSynthetic (Lorg/jetbrains/dokka/model/Documentable;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Z +public final class org/jetbrains/dokka/analysis/kotlin/sample/SampleSnippet { + public fun <init> (Ljava/util/List;Ljava/lang/String;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getBody ()Ljava/lang/String; + public final fun getImports ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } 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 index 7d434bd5..1df1dfe6 100644 --- 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 @@ -4,17 +4,21 @@ package org.jetbrains.dokka.analysis.kotlin +import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironmentCreator +import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironment import org.jetbrains.dokka.plugability.DokkaPlugin import org.jetbrains.dokka.plugability.DokkaPluginApiPreview +import org.jetbrains.dokka.plugability.ExtensionPoint import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement public class KotlinAnalysisPlugin : DokkaPlugin() { - /* - * This is where stable public API will go. + /** + * An extension for analyzing Kotlin sample functions used in the `@sample` KDoc tag. * - * No stable public API for now. + * @see SampleAnalysisEnvironment for more details */ + public val sampleAnalysisEnvironmentCreator: ExtensionPoint<SampleAnalysisEnvironmentCreator> 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/InternalKotlinAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt index 0ef1399a..d032d490 100644 --- 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 @@ -31,8 +31,6 @@ public class InternalKotlinAnalysisPlugin : DokkaPlugin() { 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/SampleProvider.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider.kt deleted file mode 100644 index 472d17f0..00000000 --- a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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/sample/SampleAnalysisEnvironment.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleAnalysisEnvironment.kt new file mode 100644 index 00000000..3620808a --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleAnalysisEnvironment.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.kotlin.sample + +import org.jetbrains.dokka.DokkaConfiguration + +/** + * Fully-configured and ready-to-use sample analysis environment. + * + * It's best to limit the scope of use and lifetime of this environment as it takes up + * additional resources which could be freed once the samples have been analyzed. + * Therefore, it's best to use it through the [SampleAnalysisEnvironmentCreator.use] lambda. + * + * For example, if you need to process all samples in an arbitrary project, it's best to do it + * in one iteration and at the same time, so that the environment is created once and lives for + * as little is possible, as opposed to creating it again and again for every individual sample. + */ +public interface SampleAnalysisEnvironment { + + /** + * Resolves a Kotlin sample function by its fully qualified name, and returns its import statements and body. + * + * @param sourceSet must be either the source set in which this sample function resides, or the source set + * for which [DokkaConfiguration#samples] or [DokkaConfiguration#sourceRoots] + * have been configured with the sample's sources. + * @param fullyQualifiedLink fully qualified path to the sample function, including all middle packages + * and the name of the function. Only links to Kotlin functions are valid, + * which can reside within a class. The package must be the same as the package + * declared in the sample file. The function must be resolvable by Dokka, + * meaning it must reside either in the main sources of the project or its + * sources must be included in [DokkaConfiguration#samples] or + * [DokkaConfiguration#sourceRoots]. Example: `com.example.pckg.topLevelKotlinFunction` + * + * @return a sample code snippet which includes import statements and the function body, + * or null if the link could not be resolved (examine the logs to find out the reason). + */ + public fun resolveSample( + sourceSet: DokkaConfiguration.DokkaSourceSet, + fullyQualifiedLink: String + ): SampleSnippet? +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleAnalysisEnvironmentCreator.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleAnalysisEnvironmentCreator.kt new file mode 100644 index 00000000..d64734ef --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleAnalysisEnvironmentCreator.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.sample + +import org.jetbrains.dokka.analysis.kotlin.KotlinAnalysisPlugin + +/** + * Entry point to analyzing Kotlin samples. + * + * Can be acquired via [KotlinAnalysisPlugin.sampleAnalysisEnvironmentCreator]. + */ +public interface SampleAnalysisEnvironmentCreator { + + /** + * Creates and configures the sample analysis environment for a limited-time use. + * + * Configuring sample analysis environment is a rather expensive operation that takes up additional + * resources since Dokka needs to configure and analyze source roots additional to the main ones. + * It's best to limit the scope of use and the lifetime of the created environment + * so that the resources could be freed as soon as possible. + * + * No specific cleanup is required by the caller - everything is taken care of automatically + * as soon as you exit the [block] block. + * + * Usage example: + * ```kotlin + * // create a short-lived environment and resolve all the needed samples + * val sample = sampleAnalysisEnvironmentCreator.use { + * resolveSample(sampleSourceSet, "org.jetbrains.dokka.sample.functionName") + * } + * // process the samples + * // ... + * ``` + */ + public fun <T> use(block: SampleAnalysisEnvironment.() -> T): T +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleSnippet.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleSnippet.kt new file mode 100644 index 00000000..41b3fa5c --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleSnippet.kt @@ -0,0 +1,45 @@ +/* + * 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.sample + +/** + * Represents a sample code snippet of a Kotlin function. The snippet includes both file + * import directives (all, even unused) and the sample function body. + * + * @property imports list of import statement values, without the `import` prefix. + * Contains no blank lines. Example of a single value: `com.example.pckg.MyClass.function`. + * @property body body of the sample function, without the function name or curly braces, only the inner body. + * Common minimal indent of all lines is trimmed. Leading and trailing line breaks are removed. + * Trailing whitespaces are removed. Example: given the sample function `fun foo() { println("foo") }`, + * the sample body will be `println("foo")`. + * + * @see SampleAnalysisEnvironment for how to acquire it + */ +public class SampleSnippet( + public val imports: List<String>, + public val body: String +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SampleSnippet + + if (imports != other.imports) return false + if (body != other.body) return false + + return true + } + + override fun hashCode(): Int { + var result = imports.hashCode() + result = 31 * result + body.hashCode() + return result + } + + override fun toString(): String { + return "SampleSnippet(imports=$imports, body='$body')" + } +} 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 index 618e28a8..3b8a2afd 100644 --- 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 @@ -4,52 +4,515 @@ package org.jetbrains.dokka.analysis.test.sample +import org.jetbrains.dokka.analysis.kotlin.sample.SampleSnippet import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject +import org.jetbrains.dokka.analysis.test.api.mixedJvmTestProject import org.jetbrains.dokka.analysis.test.api.useServices -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull +import org.jetbrains.dokka.analysis.test.api.util.CollectingDokkaConsoleLogger +import org.jetbrains.dokka.analysis.test.api.util.singleSourceSet +import org.junit.jupiter.api.Tag +import kotlin.test.* class SampleAnalysisTest { @Test - fun `should return sources of a kotlin sample`() { + fun `should resolve a valid sample if set via the samples option`() { val testProject = kotlinJvmTestProject { dokkaConfiguration { kotlinSourceSet { - additionalSourceRoots = setOf("/samples") + samples = setOf("/samples/collections.kt") } } - sampleFile("/samples/stringListOf-sample.kt", fqPackageName = "org.jetbrains.dokka.sample.generator") { + sampleFile("/samples/collections.kt", fqPackageName = "org.jetbrains.dokka.sample.collections") { +""" 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() + fun specificPositionOperations() { + val numbers = mutableListOf(1, 2, 3, 4) + numbers.add(5) + numbers.removeAt(1) + numbers[0] = 0 + numbers.shuffle() + if (numbers.size > 0) { + println(numbers) + } } """ } } testProject.useServices { context -> - val sampleSourceSet = context.configuration.sourceSets.single() + val sample = sampleAnalysisEnvironmentCreator.use { + resolveSample( + sourceSet = context.singleSourceSet(), + fullyQualifiedLink = "org.jetbrains.dokka.sample.collections.specificPositionOperations" + ) + } + assertNotNull(sample) + + val expectedImports = listOf( + "org.jetbrains.dokka.DokkaConfiguration", + "org.jetbrains.dokka.DokkaGenerator", + "org.jetbrains.dokka.utilities.DokkaLogger" + ) - val sampleProvider = sampleProviderFactory.build() - val sample = sampleProvider.getSample(sampleSourceSet, "org.jetbrains.dokka.sample.generator.runGenerator") + val expectedBody = """ + val numbers = mutableListOf(1, 2, 3, 4) + numbers.add(5) + numbers.removeAt(1) + numbers[0] = 0 + numbers.shuffle() + if (numbers.size > 0) { + println(numbers) + } + """.trimIndent() + + assertEquals(expectedImports, sample.imports) + assertEquals(expectedBody, sample.body) + } + } + + @Test + @Tag("onlyDescriptors") // TODO #3359 + fun `should resolve a valid sample if set via the additionalSourceRoots option`() { + val testProject = kotlinJvmTestProject { + dokkaConfiguration { + kotlinSourceSet { + additionalSourceRoots = setOf("/samples") + } + } + sampleFile("/samples/collections.kt", fqPackageName = "org.jetbrains.dokka.sample.collections") { + +""" + import org.jetbrains.dokka.DokkaConfiguration + import org.jetbrains.dokka.DokkaGenerator + import org.jetbrains.dokka.utilities.DokkaLogger + + fun specificPositionOperations() { + val numbers = mutableListOf(1, 2, 3, 4) + numbers.add(5) + numbers.removeAt(1) + numbers[0] = 0 + numbers.shuffle() + if (numbers.size > 0) { + println(numbers) + } + } + """ + } + } + + testProject.useServices { context -> + val sample = sampleAnalysisEnvironmentCreator.use { + resolveSample( + sourceSet = context.singleSourceSet(), + fullyQualifiedLink = "org.jetbrains.dokka.sample.collections.specificPositionOperations" + ) + } assertNotNull(sample) val expectedImports = listOf( - "import org.jetbrains.dokka.DokkaConfiguration", - "import org.jetbrains.dokka.DokkaGenerator", - "import org.jetbrains.dokka.utilities.DokkaLogger" - ).joinToString(separator = "\n") + "org.jetbrains.dokka.DokkaConfiguration", + "org.jetbrains.dokka.DokkaGenerator", + "org.jetbrains.dokka.utilities.DokkaLogger" + ) + + val expectedBody = """ + val numbers = mutableListOf(1, 2, 3, 4) + numbers.add(5) + numbers.removeAt(1) + numbers[0] = 0 + numbers.shuffle() + if (numbers.size > 0) { + println(numbers) + } + """.trimIndent() + + assertEquals(expectedImports, sample.imports) + assertEquals(expectedBody, sample.body) + } + } + + @Test + @Tag("onlyDescriptors") // TODO #3359 + fun `should resolve a valid sample function that exists in the main source set`() { + val testProject = kotlinJvmTestProject { + ktFile("org/jetbrains/dokka/test/MyKotlinFile.kt") { + +""" + import org.jetbrains.dokka.DokkaConfiguration + + fun myAverageTopLevelFunction() { + println("hello from the average top level function") + } + """ + } + } + + testProject.useServices { context -> + val sample = sampleAnalysisEnvironmentCreator.use { + resolveSample(context.singleSourceSet(), "org.jetbrains.dokka.test.myAverageTopLevelFunction") + } + assertNotNull(sample) + + val expectedImports = listOf("org.jetbrains.dokka.DokkaConfiguration") + val expectedBody = "println(\"hello from the average top level function\")" + + assertEquals(expectedImports, sample.imports) + assertEquals(expectedBody, sample.body) + } + } + + @Test + fun `should resolve a valid sample in the root package`() { + val testProject = kotlinJvmTestProject { + dokkaConfiguration { + kotlinSourceSet { + samples = setOf("/samples/TopLevelSample.kt") + } + } + + sampleFile("/samples/TopLevelSample.kt", fqPackageName = "") { + +""" + import org.jetbrains.dokka.DokkaConfiguration + + fun foo() { + println("hello from the root") + } + """ + } + } + + testProject.useServices { context -> + val sample = sampleAnalysisEnvironmentCreator.use { + resolveSample(context.singleSourceSet(), "foo") + } + assertNotNull(sample) + + val expectedImports = listOf("org.jetbrains.dokka.DokkaConfiguration") + val expectedBody = "println(\"hello from the root\")" + + assertEquals(expectedImports, sample.imports) + assertEquals(expectedBody, sample.body) + } + } + + @Test + fun `should resolve a valid sample function from a class in the root package`() { + val testProject = kotlinJvmTestProject { + dokkaConfiguration { + kotlinSourceSet { + samples = setOf("/samples/RootClassSample.kt") + } + } + + sampleFile("/samples/RootClassSample.kt", fqPackageName = "") { + +""" + import org.jetbrains.dokka.DokkaConfiguration + + class RootClass { + fun foo() { + println("hello from within a root class") + } + } + """ + } + } + + testProject.useServices { context -> + val sample = sampleAnalysisEnvironmentCreator.use { + resolveSample(context.singleSourceSet(), "RootClass.foo") + } + assertNotNull(sample) + + val expectedImports = listOf("org.jetbrains.dokka.DokkaConfiguration") + val expectedBody = "println(\"hello from within a root class\")" + + assertEquals(expectedImports, sample.imports) + assertEquals(expectedBody, sample.body) + } + } + + @Test + fun `should resolve a valid sample function from a class`() { + val testProject = kotlinJvmTestProject { + dokkaConfiguration { + kotlinSourceSet { + samples = setOf("/samples/SampleWithinClass.kt") + } + } + + sampleFile("/samples/SampleWithinClass.kt", fqPackageName = "samples") { + +""" + import org.jetbrains.dokka.DokkaConfiguration + + package samples + + class SampleWithinClass { + fun foo() { + println("hello from within a class") + } + } + """ + } + } + + testProject.useServices { context -> + val sample = sampleAnalysisEnvironmentCreator.use { + resolveSample(context.singleSourceSet(), "samples.SampleWithinClass.foo") + } + assertNotNull(sample) + + val expectedImports = listOf("org.jetbrains.dokka.DokkaConfiguration") + val expectedBody = "println(\"hello from within a class\")" + + assertEquals(expectedImports, sample.imports) + assertEquals(expectedBody, sample.body) + } + } + + @Test + fun `should return null for non-existing sample`() { + val testProject = kotlinJvmTestProject { + // nothing + } + + testProject.useServices { context -> + val nonExistingSample = sampleAnalysisEnvironmentCreator.use { + resolveSample(context.singleSourceSet(), "com.example.non.existing.sampleFunction") + } + + assertNull(nonExistingSample) + } + } + + @Test + fun `should return null if sample is resolved just by class name`() { + val testProject = kotlinJvmTestProject { + dokkaConfiguration { + kotlinSourceSet { + samples = setOf("/samples/FooSampleFile.kt") + } + } + sampleFile("/samples/FooSampleFile.kt", fqPackageName = "org.jetbrains.dokka.sample") { + +""" + import org.jetbrains.dokka.DokkaConfiguration + + fun topLevelFunction() {} + + class FooSampleClass { + fun foo() { + println("foo") + } + } + """ + } + } + + val collectingLogger = CollectingDokkaConsoleLogger() + testProject.useServices(collectingLogger) { context -> + val sampleByClassName = sampleAnalysisEnvironmentCreator.use { + resolveSample(context.singleSourceSet(), "org.jetbrains.dokka.sample.FooSampleClass") + } + assertNull(sampleByClassName) + } + + val containsNonKotlinSampleLinkLog = collectingLogger.collectedLogMessages.contains( + "Unable to process a @sample link: \"org.jetbrains.dokka.sample.FooSampleClass\". " + + "Only function links allowed." + ) + assertTrue(containsNonKotlinSampleLinkLog) + } + + @Test + @Tag("onlyDescriptors") // TODO #3359 + fun `should return null if trying to resolve a non-kotlin sample link`() { + val testProject = mixedJvmTestProject { + kotlinSourceDirectory { + javaFile("org/jetbrains/test/sample/JavaClass.java") { + +""" + public class JavaClass { + public void foo() { + System.out.println("foo"); + } + } + """ + } + ktFile("org/jetbrains/test/sample/KotlinFile.kt") { + +""" + fun foo() {} + """ + } + } + } + + val collectingLogger = CollectingDokkaConsoleLogger() + testProject.useServices(collectingLogger) { context -> + sampleAnalysisEnvironmentCreator.use { + val kotlinSourceSet = context.singleSourceSet() + + val byClassName = resolveSample(kotlinSourceSet, "org.jetbrains.test.sample.JavaClass") + assertNull(byClassName) + + val byClassFunctionName = resolveSample(kotlinSourceSet, "org.jetbrains.test.sample.JavaClass.foo") + assertNull(byClassFunctionName) + } + } + + val containsNonKotlinSampleLinkLog = collectingLogger.collectedLogMessages.contains( + "Unable to resolve non-Kotlin @sample links: \"org.jetbrains.test.sample.JavaClass\"" + ) + assertTrue(containsNonKotlinSampleLinkLog) + } + + @Test + @Tag("onlyDescriptors") // TODO #3359 + fun `should filter out empty import statement lines`() { + val testProject = kotlinJvmTestProject { + ktFile("org/jetbrains/dokka/test/MyKotlinFile.kt") { + +""" + import org.jetbrains.dokka.DokkaConfiguration + + import org.jetbrains.dokka.DokkaGenerator + + import org.jetbrains.dokka.utilities.DokkaLogger + + fun sample() { + println("hello from sample") + } + """ + } + } + + testProject.useServices { context -> + val sample = sampleAnalysisEnvironmentCreator.use { + resolveSample(context.singleSourceSet(), "org.jetbrains.dokka.test.sample") + } + assertNotNull(sample) - val expectedBody = "DokkaGenerator(configuration, logger).generate()" + val expectedImports = listOf( + "org.jetbrains.dokka.DokkaConfiguration", + "org.jetbrains.dokka.DokkaGenerator", + "org.jetbrains.dokka.utilities.DokkaLogger", + ) + val expectedBody = "println(\"hello from sample\")" assertEquals(expectedImports, sample.imports) assertEquals(expectedBody, sample.body) } } + + @Test + @Tag("onlyDescriptors") // TODO #3359 + fun `should return an empty list of imports if sample file has none`() { + val testProject = kotlinJvmTestProject { + ktFile("org/jetbrains/dokka/test/MyKotlinFile.kt") { + +""" + fun sample() { + println("hello from sample") + } + """ + } + } + + testProject.useServices { context -> + val sample = sampleAnalysisEnvironmentCreator.use { + resolveSample(context.singleSourceSet(), "org.jetbrains.dokka.test.sample") + } + assertNotNull(sample) + + assertTrue(sample.imports.isEmpty()) + + val expectedBody = "println(\"hello from sample\")" + assertEquals(expectedBody, sample.body) + + } + } + + @Test + @Tag("onlyDescriptors") // TODO #3359 + fun `should filter out leading and trailing line breaks`() { + val testProject = kotlinJvmTestProject { + ktFile("org/jetbrains/dokka/test/MyKotlinFile.kt") { + +""" + fun sample() { + + + println("hello from sample") + + + + } + """ + } + } + + testProject.useServices { context -> + val sample = sampleAnalysisEnvironmentCreator.use { + resolveSample(context.singleSourceSet(), "org.jetbrains.dokka.test.sample") + } + assertNotNull(sample) + + val expectedBody = "println(\"hello from sample\")" + assertEquals(expectedBody, sample.body) + } + } + + @Test + @Tag("onlyDescriptors") // TODO #3359 + fun `should filter out trailing whitespace`() { + val testProject = kotlinJvmTestProject { + ktFile("org/jetbrains/dokka/test/MyKotlinFile.kt") { + +""" + fun sample() { + println("hello from sample") + } + """ + } + } + + testProject.useServices { context -> + val sample = sampleAnalysisEnvironmentCreator.use { + resolveSample(context.singleSourceSet(), "org.jetbrains.dokka.test.sample") + } + assertNotNull(sample) + + val expectedBody = "println(\"hello from sample\")" + assertEquals(expectedBody, sample.body) + } + } + + @Test + fun `should see two identical snippets as equal`() { + val firstSnippet = createHardcodedSnippet() + val secondSnippet = createHardcodedSnippet() + + assertEquals(firstSnippet, secondSnippet) + } + + @Test + fun `should return same hashcode for two equal sample snippets`() { + val firstSnippet = createHardcodedSnippet() + val secondSnippet = createHardcodedSnippet() + + assertEquals(firstSnippet.hashCode(), secondSnippet.hashCode()) + } + + private fun createHardcodedSnippet(): SampleSnippet { + return SampleSnippet( + imports = listOf( + "org.jetbrains.dokka.DokkaConfiguration", + "org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet", + ), + body = """ + class Foo { + fun bar(): String = TODO() + } + """.trimIndent() + ) + } + + @Test + @Ignore // TODO [beresnev] should be implemented when there's api for KMP projects + fun `should return null for existing sample when resolving with the wrong source set`() {} } 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 index 9c0fa936..e000bb69 100644 --- 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 @@ -8,10 +8,13 @@ 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.analysis.defaultAnalysisLogger 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.CollectingDokkaConsoleLogger import org.jetbrains.dokka.analysis.test.api.util.withTempDirectory import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.utilities.DokkaLogger /** * Represents a virtual test project (as if it's user-defined) that will be used to run Dokka. @@ -66,8 +69,11 @@ interface TestProject { * * val module: DModule = testProject.parse() * ``` + * + * @param logger logger to be used for running Dokka and tests. Custom loggers like [CollectingDokkaConsoleLogger] + * can be useful in verifying the behavior. */ -fun TestProject.parse(): DModule = TestProjectAnalyzer.parse(this) +fun TestProject.parse(logger: DokkaLogger = defaultAnalysisLogger): DModule = TestProjectAnalyzer.parse(this, logger) /** * Runs Dokka on the given [TestProject] and provides not only the resulting documentable model, @@ -88,10 +94,16 @@ fun TestProject.parse(): DModule = TestProjectAnalyzer.parse(this) * val allPackageDocs: SourceSetDependent<DocumentationNode> = moduleAndPackageDocumentationReader.read(pckg) * } * ``` + * + * @param logger logger to be used for running Dokka and tests. Custom loggers like [CollectingDokkaConsoleLogger] + * can be useful in verifying the behavior. */ -fun TestProject.useServices(block: TestAnalysisServices.(context: TestAnalysisContext) -> Unit) { +fun TestProject.useServices( + logger: DokkaLogger = defaultAnalysisLogger, + block: TestAnalysisServices.(context: TestAnalysisContext) -> Unit +) { withTempDirectory { tempDirectory -> - val (services, context) = TestProjectAnalyzer.analyze(this, tempDirectory) + val (services, context) = TestProjectAnalyzer.analyze(this, tempDirectory, logger) services.block(context) } } 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 index ab70bbd4..f729838d 100644 --- 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 @@ -6,7 +6,7 @@ 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 +import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironmentCreator /** * Services exposed in [KotlinAnalysisPlugin] that are ready to be used. @@ -15,6 +15,6 @@ import org.jetbrains.dokka.analysis.kotlin.internal.SampleProviderFactory * It is analogous to calling `context.plugin<KotlinAnalysisPlugin>().querySingle { serviceName }`. */ class TestAnalysisServices( - val sampleProviderFactory: SampleProviderFactory, + val sampleAnalysisEnvironmentCreator: SampleAnalysisEnvironmentCreator, 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 index 1668b53f..674c6d47 100644 --- 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 @@ -6,6 +6,7 @@ package org.jetbrains.dokka.analysis.test.api.analysis import org.jetbrains.dokka.CoreExtensions import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.kotlin.KotlinAnalysisPlugin import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin import org.jetbrains.dokka.analysis.test.api.TestDataFile import org.jetbrains.dokka.analysis.test.api.TestProject @@ -21,16 +22,14 @@ 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.DokkaLogger 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. + * The default logger used for running Dokka and analyzing projects. */ -val analysisLogger = DokkaConsoleLogger(minLevel = LoggingLevel.INFO) +val defaultAnalysisLogger = DokkaConsoleLogger(minLevel = LoggingLevel.DEBUG) /** * Analyzer of the test projects, it is essentially a very simple Dokka runner. @@ -47,7 +46,7 @@ val analysisLogger = DokkaConsoleLogger(minLevel = LoggingLevel.INFO) * 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 { +internal object TestProjectAnalyzer { /** * A quick way to analyze a [TestProject], for cases when only the documentable @@ -58,11 +57,11 @@ object TestProjectAnalyzer { * * @see [TestProject.parse] for a user-friendly way to call it */ - fun parse(testProject: TestProject): DModule { + fun parse(testProject: TestProject, logger: DokkaLogger): 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) + return withTempDirectory(logger) { tempDirectory -> + val (_, context) = testProject.initialize(outputDirectory = tempDirectory, logger) + generateDocumentableModel(context, logger) } } @@ -80,14 +79,15 @@ object TestProjectAnalyzer { */ fun analyze( testProject: TestProject, - persistentDirectory: File + persistentDirectory: File, + logger: DokkaLogger ): Pair<TestAnalysisServices, TestAnalysisContext> { - val (dokkaConfiguration, dokkaContext) = testProject.initialize(outputDirectory = persistentDirectory) - val analysisServices = createTestAnalysisServices(dokkaContext) + val (dokkaConfiguration, dokkaContext) = testProject.initialize(outputDirectory = persistentDirectory, logger) + val analysisServices = createTestAnalysisServices(dokkaContext, logger) val testAnalysisContext = TestAnalysisContext( context = dokkaContext, configuration = dokkaConfiguration, - module = generateDocumentableModel(dokkaContext) + module = generateDocumentableModel(dokkaContext, logger) ) return analysisServices to testAnalysisContext } @@ -96,28 +96,31 @@ object TestProjectAnalyzer { * 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") + private fun TestProject.initialize( + outputDirectory: File, + logger: DokkaLogger + ): Pair<DokkaConfiguration, DokkaContext> { + logger.progress("Initializing and verifying project $this") this.verify() require(outputDirectory.isDirectory) { "outputDirectory has to exist and be a directory: $outputDirectory" } - this.initializeTestFiles(relativeToDir = outputDirectory) + this.initializeTestFiles(relativeToDir = outputDirectory, logger) - analysisLogger.progress("Creating configuration and context") + logger.progress("Creating configuration and context") val testDokkaConfiguration = this.getConfiguration() val dokkaConfiguration = testDokkaConfiguration.toDokkaConfiguration(projectDir = outputDirectory).also { it.verify() } - return dokkaConfiguration to createContext(dokkaConfiguration) + return dokkaConfiguration to createContext(dokkaConfiguration, logger) } /** * 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") + private fun TestProject.initializeTestFiles(relativeToDir: File, logger: DokkaLogger) { + logger.progress("Initializing test files relative to the \"$relativeToDir\" directory") this.getTestData().getFiles().forEach { val testDataFile = relativeToDir.resolve(it.pathFromProjectRoot.removePrefix("/")) @@ -128,7 +131,7 @@ object TestProjectAnalyzer { throw IllegalStateException("Unable to create dirs \"${testDataFile.parentFile}\"", e) } - analysisLogger.debug("Creating \"${testDataFile.absolutePath}\"") + logger.debug("Creating \"${testDataFile.absolutePath}\"") check(testDataFile.createNewFile()) { "Unable to create a test file: ${testDataFile.absolutePath}" } @@ -163,11 +166,11 @@ object TestProjectAnalyzer { } } - private fun createContext(dokkaConfiguration: DokkaConfiguration): DokkaContext { - analysisLogger.progress("Creating DokkaContext from test configuration") + private fun createContext(dokkaConfiguration: DokkaConfiguration, logger: DokkaLogger): DokkaContext { + logger.progress("Creating DokkaContext from test configuration") return DokkaContext.create( configuration = dokkaConfiguration, - logger = analysisLogger, + logger = logger, pluginOverrides = listOf() ) } @@ -176,12 +179,12 @@ object TestProjectAnalyzer { * 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") + private fun generateDocumentableModel(context: DokkaContext, logger: DokkaLogger): DModule { + logger.progress("Generating the documentable model") val sourceSetModules = context .configuration .sourceSets - .map { sourceSet -> translateSources(sourceSet, context) } + .map { sourceSet -> translateSources(sourceSet, context, logger) } .flatten() if (sourceSetModules.isEmpty()) { @@ -196,12 +199,16 @@ object TestProjectAnalyzer { * 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> { + private fun translateSources( + sourceSet: DokkaConfiguration.DokkaSourceSet, + context: DokkaContext, + logger: DokkaLogger + ): 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}") + logger.debug("Translating sources for ${sourceSet.sourceSetID}") return translators.map { it.invoke(sourceSet, context) } } @@ -212,12 +219,18 @@ object TestProjectAnalyzer { * 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>() + private fun createTestAnalysisServices( + context: DokkaContext, + logger: DokkaLogger + ): TestAnalysisServices { + logger.progress("Creating analysis services") + val publicAnalysisPlugin = context.plugin<KotlinAnalysisPlugin>() + val internalAnalysisPlugin = context.plugin<InternalKotlinAnalysisPlugin>() return TestAnalysisServices( - sampleProviderFactory = internalPlugin.querySingle { sampleProviderFactory }, - moduleAndPackageDocumentationReader = internalPlugin.querySingle { moduleAndPackageDocumentationReader } + sampleAnalysisEnvironmentCreator = publicAnalysisPlugin.querySingle { sampleAnalysisEnvironmentCreator }, + moduleAndPackageDocumentationReader = internalAnalysisPlugin.querySingle { + moduleAndPackageDocumentationReader + } ) } } 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 index 156fc4e4..baabeed7 100644 --- 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 @@ -49,7 +49,7 @@ class JavaTestSourceSetBuilder : BaseTestDokkaSourceSetBuilder() { return TestDokkaSourceSet( analysisPlatform = Platform.jvm, displayName = "JavaJvmSourceSet", - sourceSetID = DokkaSourceSetID(scopeId = "project", sourceSetName = "java"), + sourceSetID = JavaTestProject.DEFAULT_SOURCE_SET_ID, dependentSourceSets = setOf(), sourceRoots = additionalSourceRoots + setOf(JavaTestProject.DEFAULT_SOURCE_ROOT), classpath = additionalClasspath, // TODO [beresnev] is kotlin jvm stdlib needed here? 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 index 39f0f0f6..9ce85961 100644 --- 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 @@ -4,6 +4,7 @@ package org.jetbrains.dokka.analysis.test.api.jvm.java +import org.jetbrains.dokka.DokkaSourceSetID import org.jetbrains.dokka.analysis.test.api.TestData import org.jetbrains.dokka.analysis.test.api.TestDataFile import org.jetbrains.dokka.analysis.test.api.TestProject @@ -67,7 +68,8 @@ class JavaTestProject : TestProject, JavaFileCreator, MdFileCreator { ")" } - internal companion object { - internal const val DEFAULT_SOURCE_ROOT = "/src/main/java" + companion object { + const val DEFAULT_SOURCE_ROOT = "/src/main/java" + val DEFAULT_SOURCE_SET_ID = DokkaSourceSetID(scopeId = "project", sourceSetName = "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 index e5424ead..79028056 100644 --- 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 @@ -44,7 +44,7 @@ class KotlinJvmTestSourceSetBuilder : BaseTestDokkaSourceSetBuilder() { return TestDokkaSourceSet( analysisPlatform = Platform.jvm, displayName = "KotlinJvmSourceSet", - sourceSetID = DokkaSourceSetID(scopeId = "project", sourceSetName = "kotlin"), + sourceSetID = KotlinJvmTestProject.DEFAULT_SOURCE_SET_ID, dependentSourceSets = setOf(), sourceRoots = additionalSourceRoots + setOf(KotlinJvmTestProject.DEFAULT_SOURCE_ROOT), classpath = additionalClasspath + setOf(getKotlinJvmStdlibJarPath()), 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 index 178a1dc3..d67e1321 100644 --- 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 @@ -4,6 +4,7 @@ package org.jetbrains.dokka.analysis.test.api.jvm.kotlin +import org.jetbrains.dokka.DokkaSourceSetID import org.jetbrains.dokka.analysis.test.api.TestData import org.jetbrains.dokka.analysis.test.api.TestDataFile import org.jetbrains.dokka.analysis.test.api.TestProject @@ -85,8 +86,9 @@ class KotlinJvmTestProject : TestProject, KtFileCreator, MdFileCreator, KotlinSa ")" } - internal companion object { - internal const val DEFAULT_SOURCE_ROOT = "/src/main/kotlin" + companion object { + const val DEFAULT_SOURCE_ROOT = "/src/main/kotlin" + val DEFAULT_SOURCE_SET_ID = DokkaSourceSetID(scopeId = "project", sourceSetName = "kotlin") } } 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 index 32b7ce0a..45ca53e2 100644 --- 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 @@ -10,6 +10,9 @@ 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.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.markdown.MarkdownTestData import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestDataFile import org.jetbrains.dokka.analysis.test.api.markdown.MdFileCreator @@ -20,13 +23,14 @@ 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 { +class MixedJvmTestProject : TestProject, MdFileCreator, KotlinSampleFileCreator { 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() + private val kotlinSampleTestData = KotlinSampleTestData() @AnalysisTestDslMarker fun dokkaConfiguration(fillConfiguration: MixedJvmTestConfigurationBuilder.() -> Unit) { @@ -48,6 +52,15 @@ class MixedJvmTestProject : TestProject, MdFileCreator { 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() } @@ -62,7 +75,8 @@ class MixedJvmTestProject : TestProject, MdFileCreator { return flatListOf( this@MixedJvmTestProject.kotlinSourceDirectory.getFiles(), this@MixedJvmTestProject.javaSourceDirectory.getFiles(), - this@MixedJvmTestProject.markdownTestData.getFiles() + this@MixedJvmTestProject.markdownTestData.getFiles(), + this@MixedJvmTestProject.kotlinSampleTestData.getFiles() ) } } diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DokkaLoggerUtils.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DokkaLoggerUtils.kt new file mode 100644 index 00000000..87de4540 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DokkaLoggerUtils.kt @@ -0,0 +1,63 @@ +/* + * 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.DokkaConsoleLogger +import org.jetbrains.dokka.utilities.DokkaLogger +import org.jetbrains.dokka.utilities.LoggingLevel + +/** + * Prints messages to the console according to the passed `consoleMinLevel` parameter, + * and collects **ALL** log messages, regarding of the set logging level. + * + * Useful if you need to verify that user-friendly log messages were emitted, + * in case they outline actionable problems or help solve a problem and are + * considered to be a vital part of this product. + * + * The collected messages can be retrieved by invoking [collectedLogMessages]. + */ +class CollectingDokkaConsoleLogger( + consoleMinLoggingLevel: LoggingLevel = LoggingLevel.INFO +) : DokkaLogger { + + private val consoleLogger = DokkaConsoleLogger(consoleMinLoggingLevel) + private val _collectedLogMessages = mutableListOf<String>() + + val collectedLogMessages: List<String> = _collectedLogMessages + + override var warningsCount: Int + get() = consoleLogger.warningsCount + set(value) { consoleLogger.warningsCount = value } + + override var errorsCount: Int + get() = consoleLogger.errorsCount + set(value) { consoleLogger.errorsCount = value } + + + override fun debug(message: String) { + _collectedLogMessages.add(message) + consoleLogger.debug(message) + } + + override fun info(message: String) { + _collectedLogMessages.add(message) + consoleLogger.info(message) + } + + override fun progress(message: String) { + _collectedLogMessages.add(message) + consoleLogger.progress(message) + } + + override fun warn(message: String) { + _collectedLogMessages.add(message) + consoleLogger.warn(message) + } + + override fun error(message: String) { + _collectedLogMessages.add(message) + consoleLogger.error(message) + } +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/TestAnalysisApiUtils.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/TestAnalysisApiUtils.kt new file mode 100644 index 00000000..18a04ae5 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/TestAnalysisApiUtils.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.util + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaSourceSetID +import org.jetbrains.dokka.analysis.test.api.analysis.TestAnalysisContext +import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestProject +import org.jetbrains.dokka.analysis.test.api.jvm.kotlin.KotlinJvmTestProject + +/** + * @return the only existing source set or an exception + */ +fun TestAnalysisContext.singleSourceSet(): DokkaConfiguration.DokkaSourceSet { + return this.configuration.sourceSets.single() +} + +fun TestAnalysisContext.defaultKotlinSourceSet() = findSourceSetById(KotlinJvmTestProject.DEFAULT_SOURCE_SET_ID) +fun TestAnalysisContext.defaultJavaSourceSet() = findSourceSetById(JavaTestProject.DEFAULT_SOURCE_SET_ID) + +fun TestAnalysisContext.findSourceSetById(dokkaSourceSetID: DokkaSourceSetID): DokkaConfiguration.DokkaSourceSet { + return this.configuration.sourceSets.single { + it.sourceSetID == dokkaSourceSetID + } +} |