aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgnat Beresnev <ignat.beresnev@jetbrains.com>2023-11-21 11:28:45 +0100
committerGitHub <noreply@github.com>2023-11-21 11:28:45 +0100
commit6fbc2221ff309995c605161b51d4d64cbabddd51 (patch)
treea6476838442e47b3bbc03d753c74c84f58f67b1e
parent9ce37affaa2c1199807c08e13485740ea993994e (diff)
downloaddokka-6fbc2221ff309995c605161b51d4d64cbabddd51.tar.gz
dokka-6fbc2221ff309995c605161b51d4d64cbabddd51.tar.bz2
dokka-6fbc2221ff309995c605161b51d4d64cbabddd51.zip
Stabilize Sample analysis API (#3195)
-rw-r--r--dokka-subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api25
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/KotlinAnalysisPlugin.kt10
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt2
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider.kt36
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleAnalysisEnvironment.kt43
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleAnalysisEnvironmentCreator.kt38
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/sample/SampleSnippet.kt45
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/sample/SampleAnalysisTest.kt495
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProject.kt18
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt4
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt83
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaConfigurationBuilder.kt2
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestProject.kt6
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmConfigurationBuilder.kt2
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmTestProject.kt6
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestProject.kt18
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DokkaLoggerUtils.kt63
-rw-r--r--dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/TestAnalysisApiUtils.kt27
-rw-r--r--dokka-subprojects/analysis-kotlin-descriptors-compiler/api/analysis-kotlin-descriptors-compiler.api16
-rw-r--r--dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt11
-rw-r--r--dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSampleAnalysisEnvironment.kt195
-rw-r--r--dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt119
-rw-r--r--dokka-subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api15
-rw-r--r--dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt2
-rw-r--r--dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt33
-rw-r--r--dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt2
-rw-r--r--dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt4
-rw-r--r--dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt4
-rw-r--r--dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt5
-rw-r--r--dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt100
-rw-r--r--dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSampleAnalysisEnvironment.kt118
-rw-r--r--dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/transformers/pages/DefaultSamplesTransformer.kt49
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/linkableContent/LinkableContentTest.kt23
-rw-r--r--dokka-subprojects/plugin-base/src/test/resources/linkable/samples/jsMain/resources/Samples.kt2
-rw-r--r--dokka-subprojects/plugin-base/src/test/resources/linkable/samples/jvmMain/resources/Samples.kt2
35 files changed, 1216 insertions, 407 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
+ }
+}
diff --git a/dokka-subprojects/analysis-kotlin-descriptors-compiler/api/analysis-kotlin-descriptors-compiler.api b/dokka-subprojects/analysis-kotlin-descriptors-compiler/api/analysis-kotlin-descriptors-compiler.api
index 373ec268..c08cbd62 100644
--- a/dokka-subprojects/analysis-kotlin-descriptors-compiler/api/analysis-kotlin-descriptors-compiler.api
+++ b/dokka-subprojects/analysis-kotlin-descriptors-compiler/api/analysis-kotlin-descriptors-compiler.api
@@ -10,7 +10,6 @@ public final class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/Comp
public final fun getKdocFinder ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getKlibService ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getKotlinAnalysis ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
- public final fun getKotlinSampleProviderFactory ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getMockApplicationHack ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
}
@@ -85,21 +84,6 @@ public abstract class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/c
public final fun get (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisContext;
}
-public class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider : org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider {
- public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
- public fun close ()V
- public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext;
- public fun getSample (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Ljava/lang/String;)Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider$SampleSnippet;
- protected fun processBody (Lcom/intellij/psi/PsiElement;)Ljava/lang/String;
- protected fun processImports (Lcom/intellij/psi/PsiElement;)Ljava/lang/String;
-}
-
-public final class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProviderFactory : org/jetbrains/dokka/analysis/kotlin/internal/SampleProviderFactory {
- public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
- public fun build ()Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider;
- public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext;
-}
-
public final class org/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl : com/intellij/core/CoreJavaFileManager, org/jetbrains/kotlin/resolve/jvm/KotlinCliJavaFileManager {
public static final field Companion Lorg/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl$Companion;
public fun <init> (Lcom/intellij/psi/PsiManager;)V
diff --git a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt
index c59a43b2..e8ebceb0 100644
--- a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt
+++ b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt
@@ -11,6 +11,7 @@ import org.jetbrains.dokka.InternalDokkaApi
import org.jetbrains.dokka.analysis.java.BreakingAbstractionKotlinLightMethodChecker
import org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin
import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.DokkaAnalysisConfiguration
+import org.jetbrains.dokka.analysis.kotlin.KotlinAnalysisPlugin
import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.KotlinAnalysis
import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.ProjectKotlinAnalysis
import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.*
@@ -20,7 +21,6 @@ import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator.Defau
import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator.DefaultExternalDocumentablesProvider
import org.jetbrains.dokka.renderers.PostAction
import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
-import org.jetbrains.dokka.analysis.kotlin.internal.SampleProviderFactory
import org.jetbrains.dokka.plugability.*
import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation
@@ -75,13 +75,8 @@ public class CompilerDescriptorAnalysisPlugin : DokkaPlugin() {
plugin<InternalKotlinAnalysisPlugin>().fullClassHierarchyBuilder providing { DescriptorFullClassHierarchyBuilder() }
}
- /**
- * StdLib has its own a sample provider
- * So it should have a possibility to override this extension
- */
- @InternalDokkaApi
- public val kotlinSampleProviderFactory: Extension<SampleProviderFactory, *, *> by extending {
- plugin<InternalKotlinAnalysisPlugin>().sampleProviderFactory providing ::KotlinSampleProviderFactory
+ internal val descriptorSampleAnalysisEnvironmentCreator by extending {
+ plugin<KotlinAnalysisPlugin>().sampleAnalysisEnvironmentCreator providing ::DescriptorSampleAnalysisEnvironmentCreator
}
internal val descriptorSyntheticDocumentableDetector by extending {
diff --git a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSampleAnalysisEnvironment.kt b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSampleAnalysisEnvironment.kt
new file mode 100644
index 00000000..3df3d22c
--- /dev/null
+++ b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSampleAnalysisEnvironment.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.descriptors.compiler.impl
+
+import com.intellij.psi.PsiElement
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KDocFinder
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.KotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.SamplesKotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironment
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironmentCreator
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleSnippet
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.DokkaPluginApiPreview
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.load.kotlin.toSourceElement
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.KtBlockExpression
+import org.jetbrains.kotlin.psi.KtDeclarationWithBody
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
+import org.jetbrains.kotlin.resolve.lazy.ResolveSession
+import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
+import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
+
+internal class DescriptorSampleAnalysisEnvironmentCreator(
+ private val context: DokkaContext,
+) : SampleAnalysisEnvironmentCreator {
+
+ private val descriptorAnalysisPlugin = context.plugin<CompilerDescriptorAnalysisPlugin>()
+
+ override fun <T> use(block: SampleAnalysisEnvironment.() -> T): T {
+ // Run from the thread of Dispatchers.Default as it can help
+ // avoid memory leaks through the compiler's ThreadLocals.
+ // Might not be relevant if the project stops using coroutines.
+ return runBlocking(Dispatchers.Default) {
+ @OptIn(DokkaPluginApiPreview::class)
+ SamplesKotlinAnalysis(
+ sourceSets = context.configuration.sourceSets,
+ context = context,
+ projectKotlinAnalysis = descriptorAnalysisPlugin.querySingle { kotlinAnalysis }
+ ).use { kotlinAnalysis ->
+ val sampleAnalysis = DescriptorSampleAnalysisEnvironment(
+ kdocFinder = descriptorAnalysisPlugin.querySingle { kdocFinder },
+ kotlinAnalysis = kotlinAnalysis,
+ dokkaLogger = context.logger
+ )
+ block(sampleAnalysis)
+ }
+ }
+ }
+}
+
+internal class DescriptorSampleAnalysisEnvironment(
+ private val kdocFinder: KDocFinder,
+ private val kotlinAnalysis: KotlinAnalysis,
+ private val dokkaLogger: DokkaLogger,
+) : SampleAnalysisEnvironment {
+
+ override fun resolveSample(
+ sourceSet: DokkaConfiguration.DokkaSourceSet,
+ fullyQualifiedLink: String,
+ ): SampleSnippet? {
+ val resolveSession = kotlinAnalysis[sourceSet].resolveSession
+
+ val samplePsiElement = resolveSession.resolveSamplePsiElement(sourceSet, fullyQualifiedLink)
+ if (samplePsiElement == null) {
+ dokkaLogger.debug("Cannot resolve sample element for: \"$fullyQualifiedLink\"")
+ return null
+ } else if (samplePsiElement.containingFile !is KtFile) {
+ dokkaLogger.warn("Unable to resolve non-Kotlin @sample links: \"$fullyQualifiedLink\"")
+ return null
+ }
+
+ return SampleSnippet(
+ imports = processImports(samplePsiElement),
+ body = processBody(samplePsiElement)
+ )
+ }
+
+ private fun ResolveSession.resolveSamplePsiElement(
+ dokkaSourceSet: DokkaConfiguration.DokkaSourceSet,
+ fqLink: String,
+ ): PsiElement? {
+ val packageDescriptor = resolveNearestPackageDescriptor(fqLink)
+ if (packageDescriptor == null) {
+ dokkaLogger.debug(
+ "Unable to resolve package descriptor for @sample: \"$fqLink\";"
+ )
+ return null
+ }
+
+ val kdocLink = kdocFinder.resolveKDocLink(
+ fromDescriptor = packageDescriptor,
+ qualifiedName = fqLink,
+ sourceSet = dokkaSourceSet,
+ emptyBindingContext = true
+ ).firstOrNull()
+
+ if (kdocLink == null) {
+ dokkaLogger.warn(
+ "Unable to resolve a @sample link: \"$fqLink\". Is it used correctly? " +
+ "Expecting a link to a reachable (resolvable) Kotlin function."
+ )
+ return null
+ } else if (kdocLink.toSourceElement !is KotlinSourceElement) {
+ dokkaLogger.warn("Unable to resolve non-Kotlin @sample links: \"$fqLink\"")
+ return null
+ } else if (kdocLink !is FunctionDescriptor) {
+ dokkaLogger.warn("Unable to process a @sample link: \"$fqLink\". Only function links allowed.")
+ return null
+ }
+ return DescriptorToSourceUtils.descriptorToDeclaration(kdocLink)
+ }
+
+ /**
+ * Tries to resolve [fqLink]'s package.
+ *
+ * Since [fqLink] can be both a link to a top-level function and a link to a function within a class,
+ * we cannot tell for sure if [fqLink] contains a class name or not (relying on case letters is error-prone,
+ * there are exceptions). But we know for sure that the last element in the link is the function.
+ *
+ * So we start with what we think is the deepest package path, and if we cannot find a package descriptor
+ * for it - we drop one level and try again, until we find something or reach root.
+ *
+ * This function should also account for links to declarations within the root package (`""`).
+ *
+ * Here are some examples:
+ *
+ * Given [fqLink] = `com.example.ClassName.functionName`:
+ * 1) First pass, trying to resolve package `com.example.ClassName`. Failure.
+ * 2) Second pass, trying to resolve package `com.example`. Success.
+ *
+ * Given [fqLink] = `com.example.functionName`:
+ * 1) First pass, trying to resolve package `com.example`. Success.
+ *
+ * Given [fqLink] = `ClassName.functionName` (root package):
+ * 1) First pass, trying to resolve package `ClassName`. Failure.
+ * 2) Second pass, trying to resolve package `""`. Success.
+ */
+ private fun ResolveSession.resolveNearestPackageDescriptor(fqLink: String): LazyPackageDescriptor? {
+ val isRootPackage = !fqLink.contains('.')
+ val supposedPackageName = if (isRootPackage) "" else fqLink.substringBeforeLast(".")
+
+ val packageDescriptor = this.getPackageFragment(FqName(supposedPackageName))
+ if (packageDescriptor != null) {
+ return packageDescriptor
+ }
+ dokkaLogger.debug("Failed to resolve package \"$supposedPackageName\" for sample \"$fqLink\"")
+
+ if (isRootPackage) {
+ // cannot go any deeper
+ return null
+ }
+
+ return resolveNearestPackageDescriptor(supposedPackageName.substringBeforeLast("."))
+ }
+
+ private fun processImports(sampleElement: PsiElement): List<String> {
+ val psiFile = sampleElement.containingFile
+
+ val importsList = (psiFile as? KtFile)?.importList ?: return emptyList()
+ return importsList.imports
+ .map { it.text.removePrefix("import ") }
+ .filter { it.isNotBlank() }
+ }
+
+ private fun processBody(sampleElement: PsiElement): String {
+ return getSampleBody(sampleElement)
+ .trim { it == '\n' || it == '\r' }
+ .trimEnd()
+ .trimIndent()
+ }
+
+ private fun getSampleBody(sampleElement: PsiElement): String {
+ return when (sampleElement) {
+ is KtDeclarationWithBody -> {
+ when (val bodyExpression = sampleElement.bodyExpression) {
+ is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}")
+ else -> bodyExpression!!.text
+ }
+ }
+
+ else -> sampleElement.text
+ }
+ }
+}
diff --git a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt
deleted file mode 100644
index 5199abf5..00000000
--- a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt
+++ /dev/null
@@ -1,119 +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.descriptors.compiler.impl
-
-import com.intellij.psi.PsiElement
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.jetbrains.dokka.DokkaConfiguration
-import org.jetbrains.dokka.InternalDokkaApi
-import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
-import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KDocFinder
-import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.SamplesKotlinAnalysis
-import org.jetbrains.dokka.plugability.DokkaContext
-import org.jetbrains.dokka.plugability.plugin
-import org.jetbrains.dokka.plugability.querySingle
-import org.jetbrains.dokka.analysis.kotlin.internal.SampleProvider
-import org.jetbrains.dokka.analysis.kotlin.internal.SampleProviderFactory
-import org.jetbrains.dokka.plugability.DokkaPluginApiPreview
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtBlockExpression
-import org.jetbrains.kotlin.psi.KtDeclarationWithBody
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.resolve.lazy.ResolveSession
-
-public class KotlinSampleProviderFactory(
- public val context: DokkaContext
-): SampleProviderFactory {
- override fun build(): SampleProvider {
- return KotlinSampleProvider(context)
- }
-
-}
-/**
- * It's declared as open since StdLib has its own sample transformer
- * with [processBody] and [processImports]
- */
-@InternalDokkaApi
-public open class KotlinSampleProvider(
- public val context: DokkaContext
-): SampleProvider {
- private val kDocFinder: KDocFinder = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kdocFinder }
- private val analysis = lazy {
- /**
- * Run from the thread of [Dispatchers.Default]. It can help to avoid a memory leaks in `ThreadLocal`s (that keep `URLCLassLoader`)
- * since we shut down Dispatchers.Default at the end of each task (see [org.jetbrains.dokka.DokkaConfiguration.finalizeCoroutines]).
- * Currently, all `ThreadLocal`s are in a compiler/IDE codebase.
- */
- runBlocking(Dispatchers.Default) {
- @OptIn(DokkaPluginApiPreview::class)
- SamplesKotlinAnalysis(
- sourceSets = context.configuration.sourceSets,
- context = context,
- projectKotlinAnalysis = context.plugin<CompilerDescriptorAnalysisPlugin>()
- .querySingle { kotlinAnalysis }
- )
- }
- }
- protected open fun processBody(psiElement: PsiElement): String {
- val text = processSampleBody(psiElement).trim { it == '\n' || it == '\r' }.trimEnd()
- val lines = text.split("\n")
- val indent = lines.filter(String::isNotBlank).map { it.takeWhile(Char::isWhitespace).count() }.minOrNull() ?: 0
- return lines.joinToString("\n") { it.drop(indent) }
- }
-
- private fun processSampleBody(psiElement: PsiElement): String = when (psiElement) {
- is KtDeclarationWithBody -> {
- when (val bodyExpression = psiElement.bodyExpression) {
- is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}")
- else -> bodyExpression!!.text
- }
- }
- else -> psiElement.text
- }
-
- protected open fun processImports(psiElement: PsiElement): String {
- val psiFile = psiElement.containingFile
- return when(val text = (psiFile as? KtFile)?.importList?.text) {
- is String -> text
- else -> ""
- }
- }
-
- /**
- * @return [SampleProvider.SampleSnippet] or null if it has not found by [fqLink]
- */
- override fun getSample(sourceSet: DokkaConfiguration.DokkaSourceSet, fqLink: String): SampleProvider.SampleSnippet? {
- return runBlocking(Dispatchers.Default) {
- val resolveSession = analysis.value[sourceSet].resolveSession
- val psiElement = fqNameToPsiElement(resolveSession, fqLink, sourceSet)
- ?: return@runBlocking null.also { context.logger.warn("Cannot find PsiElement corresponding to $fqLink") }
- val imports =
- processImports(psiElement)
- val body = processBody(psiElement)
- return@runBlocking SampleProvider.SampleSnippet(imports, body)
- }
- }
- override fun close() {
- if(analysis.isInitialized())
- analysis.value.close()
- }
-
- private fun fqNameToPsiElement(resolveSession: ResolveSession, functionName: String, dokkaSourceSet: DokkaConfiguration.DokkaSourceSet): PsiElement? {
- val packageName = functionName.takeWhile { it != '.' }
- val descriptor = resolveSession.getPackageFragment(FqName(packageName))
- ?: return null.also { context.logger.warn("Cannot find descriptor for package $packageName") }
-
- with (kDocFinder) {
- val symbol = resolveKDocLink(
- descriptor,
- functionName,
- dokkaSourceSet,
- emptyBindingContext = true
- ).firstOrNull() ?: return null.also { context.logger.warn("Unresolved function $functionName in @sample") }
- return org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration(symbol)
- }
- }
-}
diff --git a/dokka-subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api b/dokka-subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api
index 4bddfcf1..dbd74f89 100644
--- a/dokka-subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api
+++ b/dokka-subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api
@@ -2,18 +2,3 @@ public final class org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAna
public fun <init> ()V
}
-public class org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider : org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider {
- public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
- public fun close ()V
- public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext;
- public fun getSample (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Ljava/lang/String;)Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider$SampleSnippet;
- protected fun processBody (Lcom/intellij/psi/PsiElement;)Ljava/lang/String;
- protected fun processImports (Lcom/intellij/psi/PsiElement;)Ljava/lang/String;
-}
-
-public final class org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProviderFactory : org/jetbrains/dokka/analysis/kotlin/internal/SampleProviderFactory {
- public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
- public fun build ()Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider;
- public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext;
-}
-
diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt
index d8a4e476..36492cdd 100644
--- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt
+++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt
@@ -64,7 +64,7 @@ internal fun KtAnalysisSession.getKDocDocumentationFrom(symbol: KtSymbol, logger
parseFromKDocTag(
kDocTag = kDocContent.contentTag,
- externalDri = { link -> resolveKDocLink(link).ifUnresolved { logger.logUnresolvedLink(link.getLinkText(), kdocLocation) } },
+ externalDri = { link -> resolveKDocLinkDRI(link).ifUnresolved { logger.logUnresolvedLink(link.getLinkText(), kdocLocation) } },
kdocLocation = kdocLocation
)
}
diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt
index 9a0b81bd..c719c855 100644
--- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt
+++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt
@@ -9,6 +9,7 @@ import org.jetbrains.dokka.analysis.kotlin.symbols.translators.getDRIFromSymbol
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.utilities.DokkaLogger
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
@@ -34,7 +35,23 @@ internal inline fun DRI?.ifUnresolved(action: () -> Unit): DRI? = this ?: run {
*
* @return [DRI] or null if the [link] is unresolved
*/
-internal fun KtAnalysisSession.resolveKDocTextLink(link: String, context: PsiElement? = null): DRI? {
+internal fun KtAnalysisSession.resolveKDocTextLinkDRI(link: String, context: PsiElement? = null): DRI? {
+ val kDocLink = createKDocLink(link, context)
+ return kDocLink?.let { resolveKDocLinkDRI(it) }
+}
+
+/**
+ * If the [link] is ambiguous, i.e. leads to more than one declaration,
+ * it returns deterministically any declaration.
+ *
+ * @return [KtSymbol] or null if the [link] is unresolved
+ */
+internal fun KtAnalysisSession.resolveKDocTextLinkSymbol(link: String, context: PsiElement? = null): KtSymbol? {
+ val kDocLink = createKDocLink(link, context)
+ return kDocLink?.let { resolveToSymbol(it) }
+}
+
+private fun KtAnalysisSession.createKDocLink(link: String, context: PsiElement? = null): KDocLink? {
val psiFactory = context?.let { KtPsiFactory.contextual(it) } ?: KtPsiFactory(this.useSiteModule.project)
val kDoc = psiFactory.createComment(
"""
@@ -43,8 +60,8 @@ internal fun KtAnalysisSession.resolveKDocTextLink(link: String, context: PsiEle
*/
""".trimIndent()
) as? KDoc
- val kDocLink = kDoc?.getDefaultSection()?.children?.filterIsInstance<KDocLink>()?.singleOrNull()
- return kDocLink?.let { resolveKDocLink(it) }
+
+ return kDoc?.getDefaultSection()?.children?.filterIsInstance<KDocLink>()?.singleOrNull()
}
/**
@@ -53,9 +70,13 @@ internal fun KtAnalysisSession.resolveKDocTextLink(link: String, context: PsiEle
*
* @return [DRI] or null if the [link] is unresolved
*/
-internal fun KtAnalysisSession.resolveKDocLink(link: KDocLink): DRI? {
- val lastNameSegment = link.children.filterIsInstance<KDocName>().lastOrNull()
- val linkedSymbol = lastNameSegment?.mainReference?.resolveToSymbols()?.firstOrNull()
+internal fun KtAnalysisSession.resolveKDocLinkDRI(link: KDocLink): DRI? {
+ val linkedSymbol = resolveToSymbol(link)
return if (linkedSymbol == null) null
else getDRIFromSymbol(linkedSymbol)
}
+
+private fun KtAnalysisSession.resolveToSymbol(kDocLink: KDocLink): KtSymbol? {
+ val lastNameSegment = kDocLink.children.filterIsInstance<KDocName>().lastOrNull()
+ return lastNameSegment?.mainReference?.resolveToSymbols()?.firstOrNull()
+}
diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt
index 4622ffd8..9f2e6873 100644
--- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt
+++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt
@@ -54,7 +54,7 @@ internal fun KtAnalysisSession.getGeneratedKDocDocumentationFrom(symbol: KtSymbo
private fun KtAnalysisSession.loadTemplate(filePath: String): DocumentationNode? {
val kdoc = loadContent(filePath) ?: return null
val externalDriProvider = { link: String ->
- resolveKDocTextLink(link)
+ resolveKDocTextLinkDRI(link)
}
val parser = MarkdownParser(externalDriProvider, filePath)
diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt
index 0ee95e45..846ee61d 100644
--- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt
+++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt
@@ -11,7 +11,7 @@ import org.jetbrains.dokka.analysis.java.parsers.DocCommentParser
import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.*
import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.logUnresolvedLink
import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.parseFromKDocTag
-import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocLink
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocLinkDRI
import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin
import org.jetbrains.dokka.model.doc.DocumentationNode
import org.jetbrains.dokka.plugability.DokkaContext
@@ -42,7 +42,7 @@ internal class KotlinDocCommentParser(
return analyze(kotlinAnalysis.getModule(sourceSet)) {
parseFromKDocTag(
kDocTag = element.comment,
- externalDri = { link -> resolveKDocLink(link).ifUnresolved { context.logger.logUnresolvedLink(link.getLinkText(), elementName) } },
+ externalDri = { link -> resolveKDocLinkDRI(link).ifUnresolved { context.logger.logUnresolvedLink(link.getLinkText(), elementName) } },
kdocLocation = null,
parseWithChildren = parseWithChildren
)
diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt
index f5cfbdb9..1fdebe24 100644
--- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt
+++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt
@@ -10,7 +10,7 @@ import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.logUnresolvedLink
import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.KotlinAnalysis
import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs.ModuleAndPackageDocumentation.Classifier.Module
import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs.ModuleAndPackageDocumentation.Classifier.Package
-import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocTextLink
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocTextLinkDRI
import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser
import org.jetbrains.dokka.model.doc.DocumentationNode
import org.jetbrains.dokka.utilities.DokkaLogger
@@ -47,7 +47,7 @@ internal fun ModuleAndPackageDocumentationParsingContext(
MarkdownParser(
externalDri = { link ->
analyze(sourceModule) {
- resolveKDocTextLink(
+ resolveKDocTextLinkDRI(
link,
contextPsi
).ifUnresolved {
diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt
index 122e0b10..23bb0dc5 100644
--- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt
+++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt
@@ -9,6 +9,7 @@ import com.intellij.psi.PsiAnnotation
import org.jetbrains.dokka.CoreExtensions
import org.jetbrains.dokka.analysis.java.BreakingAbstractionKotlinLightMethodChecker
import org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin
+import org.jetbrains.dokka.analysis.kotlin.KotlinAnalysisPlugin
import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java.KotlinInheritDocTagContentProvider
import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java.DescriptorKotlinDocCommentCreator
@@ -117,8 +118,8 @@ public class SymbolsAnalysisPlugin : DokkaPlugin() {
plugin<InternalKotlinAnalysisPlugin>().externalDocumentablesProvider providing ::SymbolExternalDocumentablesProvider
}
- internal val kotlinSampleProviderFactory by extending {
- plugin<InternalKotlinAnalysisPlugin>().sampleProviderFactory providing ::KotlinSampleProviderFactory
+ internal val symbolSampleAnalysisEnvironmentCreator by extending {
+ plugin<KotlinAnalysisPlugin>().sampleAnalysisEnvironmentCreator providing ::SymbolSampleAnalysisEnvironmentCreator
}
@OptIn(DokkaPluginApiPreview::class)
diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt
deleted file mode 100644
index e453c72d..00000000
--- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt
+++ /dev/null
@@ -1,100 +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.symbols.services
-
-import com.intellij.psi.PsiElement
-import org.jetbrains.dokka.DokkaConfiguration
-import org.jetbrains.dokka.InternalDokkaApi
-import org.jetbrains.dokka.plugability.DokkaContext
-import org.jetbrains.dokka.plugability.plugin
-import org.jetbrains.dokka.plugability.querySingle
-import org.jetbrains.dokka.analysis.kotlin.internal.SampleProvider
-import org.jetbrains.dokka.analysis.kotlin.internal.SampleProviderFactory
-import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SamplesKotlinAnalysis
-import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin
-import org.jetbrains.kotlin.analysis.api.analyze
-import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.psi.KtBlockExpression
-import org.jetbrains.kotlin.psi.KtDeclarationWithBody
-import org.jetbrains.kotlin.psi.KtFile
-
-public class KotlinSampleProviderFactory(
- public val context: DokkaContext
-): SampleProviderFactory {
- override fun build(): SampleProvider {
- return KotlinSampleProvider(context)
- }
-
-}
-/**
- * It's declared as open since StdLib has its own sample transformer
- * with [processBody] and [processImports]
- */
-@InternalDokkaApi
-public open class KotlinSampleProvider(
- public val context: DokkaContext
-): SampleProvider {
- private val kotlinAnalysisOfRegularSources = context.plugin<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis }
-
- private val kotlinAnalysisOfSamples = SamplesKotlinAnalysis(
- sourceSets = context.configuration.sourceSets, context = context
- )
-
- protected open fun processBody(psiElement: PsiElement): String {
- val text = processSampleBody(psiElement).trim { it == '\n' || it == '\r' }.trimEnd()
- val lines = text.split("\n")
- val indent = lines.filter(String::isNotBlank).minOfOrNull { it.takeWhile(Char::isWhitespace).count() } ?: 0
- return lines.joinToString("\n") { it.drop(indent) }
- }
-
- private fun processSampleBody(psiElement: PsiElement): String = when (psiElement) {
- is KtDeclarationWithBody -> {
- when (val bodyExpression = psiElement.bodyExpression) {
- is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}")
- else -> bodyExpression!!.text
- }
- }
- else -> psiElement.text
- }
-
- protected open fun processImports(psiElement: PsiElement): String {
- val psiFile = psiElement.containingFile
- return when(val text = (psiFile as? KtFile)?.importList?.text) {
- is String -> text
- else -> ""
- }
- }
-
- /**
- * @return [SampleProvider.SampleSnippet] or null if it has not found by [fqLink]
- */
- override fun getSample(sourceSet: DokkaConfiguration.DokkaSourceSet, fqLink: String): SampleProvider.SampleSnippet? {
- return kotlinAnalysisOfSamples.getModuleOrNull(sourceSet)?.let { getSampleFromModule(it, fqLink) }
- ?: getSampleFromModule(
- kotlinAnalysisOfRegularSources.getModule(sourceSet), fqLink
- )
- }
- private fun getSampleFromModule(module: KtSourceModule, fqLink: String): SampleProvider.SampleSnippet? {
- val psiElement = analyze(module) {
- val lastDotIndex = fqLink.lastIndexOf('.')
-
- val functionName = if (lastDotIndex == -1) fqLink else fqLink.substring(lastDotIndex + 1, fqLink.length)
- val packageName = if (lastDotIndex == -1) "" else fqLink.substring(0, lastDotIndex)
- getTopLevelCallableSymbols(FqName(packageName), Name.identifier(functionName)).firstOrNull()?.psi
- }
- ?: return null.also { context.logger.warn("Cannot find PsiElement corresponding to $fqLink") }
- val imports =
- processImports(psiElement)
- val body = processBody(psiElement)
-
- return SampleProvider.SampleSnippet(imports, body)
- }
-
- override fun close() {
- kotlinAnalysisOfSamples.close()
- }
-}
diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSampleAnalysisEnvironment.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSampleAnalysisEnvironment.kt
new file mode 100644
index 00000000..37095df5
--- /dev/null
+++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSampleAnalysisEnvironment.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.symbols.services
+
+import com.intellij.psi.PsiElement
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironment
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironmentCreator
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleSnippet
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocTextLinkSymbol
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.KotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SamplesKotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.analysis.api.symbols.sourcePsiSafe
+import org.jetbrains.kotlin.idea.KotlinLanguage
+import org.jetbrains.kotlin.psi.KtBlockExpression
+import org.jetbrains.kotlin.psi.KtDeclarationWithBody
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.psi.KtFunction
+
+internal class SymbolSampleAnalysisEnvironmentCreator(
+ private val context: DokkaContext,
+) : SampleAnalysisEnvironmentCreator {
+
+ private val projectKotlinAnalysis = context.plugin<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis }
+
+ override fun <T> use(block: SampleAnalysisEnvironment.() -> T): T {
+ return runBlocking(Dispatchers.Default) {
+ SamplesKotlinAnalysis(
+ sourceSets = context.configuration.sourceSets,
+ context = context
+ ).use { samplesKotlinAnalysis ->
+ val sampleAnalysisEnvironment = SymbolSampleAnalysisEnvironment(
+ samplesKotlinAnalysis = samplesKotlinAnalysis,
+ projectKotlinAnalysis = projectKotlinAnalysis,
+ dokkaLogger = context.logger
+ )
+ block(sampleAnalysisEnvironment)
+ }
+ }
+ }
+}
+
+private class SymbolSampleAnalysisEnvironment(
+ private val samplesKotlinAnalysis: KotlinAnalysis,
+ private val projectKotlinAnalysis: KotlinAnalysis,
+ private val dokkaLogger: DokkaLogger,
+) : SampleAnalysisEnvironment {
+
+ override fun resolveSample(sourceSet: DokkaSourceSet, fullyQualifiedLink: String): SampleSnippet? {
+ val psiElement = findPsiElement(sourceSet, fullyQualifiedLink)
+ if (psiElement == null) {
+ dokkaLogger.warn(
+ "Unable to resolve a @sample link: \"$fullyQualifiedLink\". Is it used correctly? " +
+ "Expecting a link to a reachable (resolvable) top-level Kotlin function."
+ )
+ return null
+ } else if (psiElement.language != KotlinLanguage.INSTANCE) {
+ dokkaLogger.warn("Unable to resolve non-Kotlin @sample links: \"$fullyQualifiedLink\"")
+ return null
+ } else if (psiElement !is KtFunction) {
+ dokkaLogger.warn("Unable to process a @sample link: \"$fullyQualifiedLink\". Only function links allowed.")
+ return null
+ }
+
+ val imports = processImports(psiElement)
+ val body = processBody(psiElement)
+
+ return SampleSnippet(imports, body)
+ }
+
+ private fun findPsiElement(sourceSet: DokkaSourceSet, fqLink: String): PsiElement? {
+ val ktSourceModule = samplesKotlinAnalysis.getModuleOrNull(sourceSet)
+ ?: projectKotlinAnalysis.getModule(sourceSet)
+
+ return analyze(ktSourceModule) {
+ resolveKDocTextLinkSymbol(fqLink)
+ ?.sourcePsiSafe()
+ }
+ }
+
+ private fun processImports(psiElement: PsiElement): List<String> {
+ val psiFile = psiElement.containingFile
+ val importsList = (psiFile as? KtFile)?.importList ?: return emptyList()
+ return importsList.imports
+ .map { it.text.removePrefix("import ") }
+ .filter { it.isNotBlank() }
+ }
+
+ private fun processBody(sampleElement: PsiElement): String {
+ return getSampleBody(sampleElement)
+ .trim { it == '\n' || it == '\r' }
+ .trimEnd()
+ .trimIndent()
+ }
+
+ private fun getSampleBody(sampleElement: PsiElement): String {
+ return when (sampleElement) {
+ is KtDeclarationWithBody -> {
+ when (val bodyExpression = sampleElement.bodyExpression) {
+ is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}")
+ else -> bodyExpression!!.text
+ }
+ }
+
+ else -> sampleElement.text
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/transformers/pages/DefaultSamplesTransformer.kt b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/transformers/pages/DefaultSamplesTransformer.kt
index 1ba049c8..6e4fef4e 100644
--- a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/transformers/pages/DefaultSamplesTransformer.kt
+++ b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/transformers/pages/DefaultSamplesTransformer.kt
@@ -4,6 +4,7 @@
package org.jetbrains.dokka.base.transformers.pages
+import org.jetbrains.dokka.analysis.kotlin.KotlinAnalysisPlugin
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DisplaySourceSet
import org.jetbrains.dokka.model.doc.Sample
@@ -13,18 +14,18 @@ import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.plugin
import org.jetbrains.dokka.plugability.querySingle
import org.jetbrains.dokka.transformers.pages.PageTransformer
-import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
-import org.jetbrains.dokka.analysis.kotlin.internal.SampleProvider
-import org.jetbrains.dokka.analysis.kotlin.internal.SampleProviderFactory
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironmentCreator
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleSnippet
internal const val KOTLIN_PLAYGROUND_SCRIPT = "https://unpkg.com/kotlin-playground@1/dist/playground.min.js"
internal class DefaultSamplesTransformer(val context: DokkaContext) : PageTransformer {
- private val sampleProviderFactory: SampleProviderFactory = context.plugin<InternalKotlinAnalysisPlugin>().querySingle { sampleProviderFactory }
+ private val sampleAnalysisEnvironment: SampleAnalysisEnvironmentCreator =
+ context.plugin<KotlinAnalysisPlugin>().querySingle { sampleAnalysisEnvironmentCreator }
override fun invoke(input: RootPageNode): RootPageNode {
- return sampleProviderFactory.build().use { sampleProvider ->
+ return sampleAnalysisEnvironment.use {
input.transformContentPagesTree { page ->
val samples = (page as? WithDocumentables)?.documentables?.flatMap {
it.documentation.entries.flatMap { entry ->
@@ -33,7 +34,7 @@ internal class DefaultSamplesTransformer(val context: DokkaContext) : PageTransf
} ?: return@transformContentPagesTree page
val newContent = samples.fold(page.content) { acc, (sampleSourceSet, sample) ->
- sampleProvider.getSample(sampleSourceSet, sample.name)
+ resolveSample(sampleSourceSet, sample.name)
?.let {
acc.addSample(page, sample.name, it)
} ?: acc
@@ -51,14 +52,44 @@ internal class DefaultSamplesTransformer(val context: DokkaContext) : PageTransf
private fun ContentNode.addSample(
contentPage: ContentPage,
fqLink: String,
- sample: SampleProvider.SampleSnippet,
+ sample: SampleSnippet,
): ContentNode {
val node = contentCode(contentPage.content.sourceSets, contentPage.dri, createSampleBody(sample.imports, sample.body), "kotlin")
return dfs(fqLink, node)
}
- fun createSampleBody(imports: String, body: String) =
- """ |$imports
+ /**
+ * If both [imports] and [body] are present, it should return
+ *
+ * ```kotlin
+ * import com.example.One
+ * import com.example.Two
+ *
+ * fun main() {
+ * //sampleStart
+ * println("Sample function body")
+ * println("Another line")
+ * //sampleEnd
+ * }
+ * ```
+ *
+ * If [imports] are empty, it should return:
+ *
+ * ```kotlin
+ * fun main() {
+ * //sampleStart
+ * println("Sample function body")
+ * println("Another line")
+ * //sampleEnd
+ * }
+ * ```
+ *
+ * Notice the presence/absence of the new line before the body.
+ */
+ private fun createSampleBody(imports: List<String>, body: String) =
+ // takeIf {} is needed so that joinToString's postfix is not added for empty lists,
+ // and trimMargin() then removes the first empty line
+ """ |${imports.takeIf { it.isNotEmpty() }?.joinToString(separator = "\n", postfix = "\n") { "import $it" } ?: "" }
|fun main() {
| //sampleStart
| $body
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/linkableContent/LinkableContentTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/linkableContent/LinkableContentTest.kt
index 1b73ffee..f03969ac 100644
--- a/dokka-subprojects/plugin-base/src/test/kotlin/linkableContent/LinkableContentTest.kt
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/linkableContent/LinkableContentTest.kt
@@ -6,6 +6,7 @@ package linkableContent
import org.jetbrains.dokka.SourceLinkDefinitionImpl
import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.base.transformers.pages.DefaultSamplesTransformer
import org.jetbrains.dokka.base.transformers.pages.sourcelinks.SourceLinksTransformer
import org.jetbrains.dokka.model.WithGenerics
import org.jetbrains.dokka.model.dfs
@@ -192,10 +193,8 @@ class LinkableContentTest : BaseAbstractTest() {
}
testFromData(configuration) {
- renderingStage = { rootPageNode, _ ->
- // TODO [beresnev] :(((
-// val newRoot = DefaultSamplesTransformer(dokkaContext).invoke(rootPageNode)
- val newRoot = rootPageNode
+ renderingStage = { rootPageNode, dokkaContext ->
+ val newRoot = DefaultSamplesTransformer(dokkaContext).invoke(rootPageNode)
val moduleChildren = newRoot.children
assertEquals(1, moduleChildren.size)
val packageChildren = moduleChildren.first().children
@@ -212,12 +211,16 @@ class LinkableContentTest : BaseAbstractTest() {
.let { it as ContentCodeBlock }.children.single()
.let { it as ContentText }.text
assertEquals(
- """|import p2.${name}Class
- |fun main() {
- | //sampleStart
- | ${name}Class().printWithExclamation("Hi, $name")
- | //sampleEnd
- |}""".trimMargin(),
+ """
+ |import p2.${name}Class
+ |import kotlin.collections.List
+ |import kotlin.collections.Map
+ |
+ |fun main() {
+ | //sampleStart
+ | ${name}Class().printWithExclamation("Hi, $name")
+ | //sampleEnd
+ |}""".trimMargin(),
text
)
}
diff --git a/dokka-subprojects/plugin-base/src/test/resources/linkable/samples/jsMain/resources/Samples.kt b/dokka-subprojects/plugin-base/src/test/resources/linkable/samples/jsMain/resources/Samples.kt
index 5dc791d7..3ea25268 100644
--- a/dokka-subprojects/plugin-base/src/test/resources/linkable/samples/jsMain/resources/Samples.kt
+++ b/dokka-subprojects/plugin-base/src/test/resources/linkable/samples/jsMain/resources/Samples.kt
@@ -5,6 +5,8 @@
package samples
import p2.JsClass
+import kotlin.collections.List
+import kotlin.collections.Map
class SamplesJs {
diff --git a/dokka-subprojects/plugin-base/src/test/resources/linkable/samples/jvmMain/resources/Samples.kt b/dokka-subprojects/plugin-base/src/test/resources/linkable/samples/jvmMain/resources/Samples.kt
index f32538cc..0a618b2a 100644
--- a/dokka-subprojects/plugin-base/src/test/resources/linkable/samples/jvmMain/resources/Samples.kt
+++ b/dokka-subprojects/plugin-base/src/test/resources/linkable/samples/jvmMain/resources/Samples.kt
@@ -5,6 +5,8 @@
package samples
import p2.JvmClass
+import kotlin.collections.List
+import kotlin.collections.Map
class SamplesJvm {