Skip to content

Commit

Permalink
Fix and refactor Sample Transformer (#3102)
Browse files Browse the repository at this point in the history
(cherry picked from commit 2fd8e90)
  • Loading branch information
vmishenev authored and IgnatBeresnev committed Aug 8, 2023
1 parent b8cf2fd commit 9d7c372
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 19 deletions.
16 changes: 8 additions & 8 deletions plugins/base/src/main/kotlin/DokkaBase.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@file:Suppress("unused")

package org.jetbrains.dokka.base

import org.jetbrains.dokka.CoreExtensions
Expand All @@ -19,6 +17,7 @@ import org.jetbrains.dokka.base.signatures.KotlinSignatureProvider
import org.jetbrains.dokka.base.signatures.SignatureProvider
import org.jetbrains.dokka.base.templating.ImmediateHtmlCommandConsumer
import org.jetbrains.dokka.base.transformers.documentables.*
import org.jetbrains.dokka.base.transformers.pages.DefaultSamplesTransformer
import org.jetbrains.dokka.base.transformers.pages.annotations.SinceKotlinTransformer
import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter
Expand All @@ -33,6 +32,7 @@ import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement
import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer
import org.jetbrains.dokka.transformers.pages.PageTransformer

@Suppress("unused")
class DokkaBase : DokkaPlugin() {

val preMergeDocumentableTransformer by extensionPoint<PreMergeDocumentableTransformer>()
Expand Down Expand Up @@ -149,7 +149,6 @@ class DokkaBase : DokkaPlugin() {

val pageMerger by extending {
CoreExtensions.pageTransformer providing ::PageMerger order {
// TODO [beresnev] make last() or at least after samples transformer
}
}

Expand Down Expand Up @@ -191,6 +190,12 @@ class DokkaBase : DokkaPlugin() {
htmlPreprocessors with RootCreator applyIf { !delayTemplateSubstitution }
}

val defaultSamplesTransformer by extending {
CoreExtensions.pageTransformer providing ::DefaultSamplesTransformer order {
before(pageMerger)
}
}

val sourceLinksTransformer by extending {
htmlPreprocessors providing ::SourceLinksTransformer order { after(rootCreator) }
}
Expand Down Expand Up @@ -270,11 +275,6 @@ class DokkaBase : DokkaPlugin() {
val defaultKotlinAnalysis: org.jetbrains.dokka.plugability.Extension<org.jetbrains.dokka.analysis.KotlinAnalysis, *, *>
get() = throw org.jetbrains.dokka.base.deprecated.AnalysisApiDeprecatedError()

@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated(message = org.jetbrains.dokka.base.deprecated.ANALYSIS_API_DEPRECATION_MESSAGE, level = DeprecationLevel.ERROR)
val defaultSamplesTransformer: org.jetbrains.dokka.plugability.Extension<PageTransformer, *, *>
get() = throw org.jetbrains.dokka.base.deprecated.AnalysisApiDeprecatedError()

@Suppress("DEPRECATION_ERROR", "DeprecatedCallableAddReplaceWith")
@Deprecated(message = org.jetbrains.dokka.base.deprecated.ANALYSIS_API_DEPRECATION_MESSAGE, level = DeprecationLevel.ERROR)
val defaultExternalDocumentablesProvider: org.jetbrains.dokka.plugability.Extension<org.jetbrains.dokka.base.translators.descriptors.ExternalDocumentablesProvider, *, *>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,29 +91,30 @@ class DefaultTemplateModelFactory(val context: DokkaContext) : TemplateModelFact
get() = URI(this).isAbsolute

private fun Appendable.resourcesForPage(pathToRoot: String, resources: List<String>): Unit =
resources.forEach {
resources.forEach { resource ->

val resourceHtml = with(createHTML()) {
when {
it.URIExtension == "css" ->

resource.URIExtension == "css" ->
link(
rel = LinkRel.stylesheet,
href = if (it.isAbsolute) it else "$pathToRoot$it"
href = if (resource.isAbsolute) resource else "$pathToRoot$resource"
)
it.URIExtension == "js" ->
resource.URIExtension == "js" ->
script(
type = ScriptType.textJavaScript,
src = if (it.isAbsolute) it else "$pathToRoot$it"
src = if (resource.isAbsolute) resource else "$pathToRoot$resource"
) {
if (it == "scripts/main.js" || it.endsWith("_deferred.js"))
if (resource == "scripts/main.js" || resource.endsWith("_deferred.js"))
defer = true
else
async = true
}
it.isImage() -> link(href = if (it.isAbsolute) it else "$pathToRoot$it")
resource.isImage() -> link(href = if (resource.isAbsolute) resource else "$pathToRoot$resource")
else -> null
}
}

if (resourceHtml != null) {
append(resourceHtml)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.jetbrains.dokka.base.transformers.pages

import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DisplaySourceSet
import org.jetbrains.dokka.model.doc.Sample
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.pages.*
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

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 }

override fun invoke(input: RootPageNode): RootPageNode {
return sampleProviderFactory.build().use { sampleProvider ->
input.transformContentPagesTree { page ->
val samples = (page as? WithDocumentables)?.documentables?.flatMap {
it.documentation.entries.flatMap { entry ->
entry.value.children.filterIsInstance<Sample>().map { entry.key to it }
}
} ?: return@transformContentPagesTree page

val newContent = samples.fold(page.content) { acc, (sampleSourceSet, sample) ->
sampleProvider.getSample(sampleSourceSet, sample.name)
?.let {
acc.addSample(page, sample.name, it)
} ?: acc
}

page.modified(
content = newContent,
embeddedResources = page.embeddedResources + KOTLIN_PLAYGROUND_SCRIPT
)
}
}
}


private fun ContentNode.addSample(
contentPage: ContentPage,
fqLink: String,
sample: SampleProvider.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
|fun main() {
| //sampleStart
| $body
| //sampleEnd
|}""".trimMargin()

private fun ContentNode.dfs(fqName: String, node: ContentCodeBlock): ContentNode {
return when (this) {
is ContentHeader -> copy(children.map { it.dfs(fqName, node) })
is ContentDivergentGroup -> @Suppress("UNCHECKED_CAST") copy(children.map {
it.dfs(fqName, node)
} as List<ContentDivergentInstance>)
is ContentDivergentInstance -> copy(
before.let { it?.dfs(fqName, node) },
divergent.dfs(fqName, node),
after.let { it?.dfs(fqName, node) })
is ContentCodeBlock -> copy(children.map { it.dfs(fqName, node) })
is ContentCodeInline -> copy(children.map { it.dfs(fqName, node) })
is ContentDRILink -> copy(children.map { it.dfs(fqName, node) })
is ContentResolvedLink -> copy(children.map { it.dfs(fqName, node) })
is ContentEmbeddedResource -> copy(children.map { it.dfs(fqName, node) })
is ContentTable -> copy(children = children.map { it.dfs(fqName, node) as ContentGroup })
is ContentList -> copy(children.map { it.dfs(fqName, node) })
is ContentGroup -> copy(children.map { it.dfs(fqName, node) })
is PlatformHintedContent -> copy(inner.dfs(fqName, node))
is ContentText -> if (text == fqName) node else this
is ContentBreakLine -> this
else -> this.also { context.logger.error("Could not recognize $this ContentNode in SamplesTransformer") }
}
}

private fun contentCode(
sourceSets: Set<DisplaySourceSet>,
dri: Set<DRI>,
content: String,
language: String,
styles: Set<Style> = emptySet(),
extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) =
ContentCodeBlock(
children = listOf(
ContentText(
text = content,
dci = DCI(dri, ContentKind.Sample),
sourceSets = sourceSets,
style = emptySet(),
extra = PropertyContainer.empty()
)
),
language = language,
dci = DCI(dri, ContentKind.Sample),
sourceSets = sourceSets,
style = styles + ContentStyle.RunnableSample + TextStyle.Monospace,
extra = extra
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package content.samples

import matchers.content.*
import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
import org.jetbrains.dokka.base.transformers.pages.KOTLIN_PLAYGROUND_SCRIPT
import org.jetbrains.dokka.model.DisplaySourceSet
import org.junit.jupiter.api.Test
import utils.TestOutputWriterPlugin
import utils.classSignature
import utils.findTestType
import java.nio.file.Paths
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals

class ContentForSamplesTest : BaseAbstractTest() {
private val testDataDir = getTestDataDir("content/samples").toAbsolutePath()
Expand Down Expand Up @@ -61,6 +64,7 @@ class ContentForSamplesTest : BaseAbstractTest() {

@Test
fun `samples block is rendered in the description`() {
val writerPlugin = TestOutputWriterPlugin()
testInline(
"""
|/src/main/kotlin/test/source.kt
Expand All @@ -70,10 +74,12 @@ class ContentForSamplesTest : BaseAbstractTest() {
| * @sample [test.sampleForClassDescription]
| */
|class Foo
""".trimIndent(), testConfiguration
""".trimIndent(), testConfiguration,
pluginOverrides = listOf(writerPlugin)
) {
pagesTransformationStage = { module ->
val page = module.findTestType("test", "Foo")
assert(KOTLIN_PLAYGROUND_SCRIPT in page.embeddedResources)
page.content.assertNode {
group {
header(1) { +"Foo" }
Expand Down Expand Up @@ -101,6 +107,9 @@ class ContentForSamplesTest : BaseAbstractTest() {
skipAllNotMatching()
}
}
renderingStage = { _, _ ->
assertNotEquals(-1, writerPlugin.writer.contents["root/test/-foo/index.html"]?.indexOf(KOTLIN_PLAYGROUND_SCRIPT))
}
}
}

Expand Down Expand Up @@ -134,6 +143,7 @@ class ContentForSamplesTest : BaseAbstractTest() {
) {
pagesTransformationStage = { module ->
val page = module.findTestType("pageMerger", "Parent")
assert(KOTLIN_PLAYGROUND_SCRIPT in page.embeddedResources)
page.content.assertNode {
group {
header(1) { +"Parent" }
Expand Down
15 changes: 15 additions & 0 deletions subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ 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;
}

Expand All @@ -64,6 +65,20 @@ 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 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/internal/SampleProviderFactory {
public abstract fun build ()Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider;
}

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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class InternalKotlinAnalysisPlugin : DokkaPlugin() {

val documentableSourceLanguageParser by extensionPoint<DocumentableSourceLanguageParser>()

val sampleProviderFactory by extensionPoint<SampleProviderFactory>()

@OptIn(DokkaPluginApiPreview::class)
override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.jetbrains.dokka.analysis.kotlin.internal

import org.jetbrains.dokka.DokkaConfiguration
import org.jetbrains.dokka.InternalDokkaApi

@InternalDokkaApi
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.
*/
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
interface SampleProvider: AutoCloseable {
class SampleSnippet(val imports: String, val body:String)


/**
* @return [SampleSnippet] or null if it has not found by [fqLink]
*/
fun getSample(sourceSet: DokkaConfiguration.DokkaSourceSet, fqLink: String): SampleSnippet?
}
15 changes: 15 additions & 0 deletions subprojects/analysis-kotlin-descriptors/compiler/api/compiler.api
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,18 @@ 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;
}

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.jetbrains.dokka.renderers.PostAction
import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation

@Suppress("unused")
@InternalDokkaApi
class CompilerDescriptorAnalysisPlugin : DokkaPlugin() {

Expand All @@ -42,7 +43,7 @@ class CompilerDescriptorAnalysisPlugin : DokkaPlugin() {
plugin<InternalKotlinAnalysisPlugin>().documentableSourceLanguageParser providing { CompilerDocumentableSourceLanguageParser() }
}

internal val defaultKotlinAnalysis by extending {
internal val defaultKotlinAnalysis by extending {
kotlinAnalysis providing { ctx ->
ProjectKotlinAnalysis(
sourceSets = ctx.configuration.sourceSets,
Expand All @@ -51,7 +52,7 @@ class CompilerDescriptorAnalysisPlugin : DokkaPlugin() {
}
}

internal val descriptorToDocumentableTranslator by extending {
internal val descriptorToDocumentableTranslator by extending {
CoreExtensions.sourceToDocumentableTranslator providing ::DefaultDescriptorToDocumentableTranslator
}

Expand All @@ -63,6 +64,10 @@ class CompilerDescriptorAnalysisPlugin : DokkaPlugin() {
plugin<InternalKotlinAnalysisPlugin>().fullClassHierarchyBuilder providing { DescriptorFullClassHierarchyBuilder() }
}

internal val kotlinSampleProviderFactory by extending {
plugin<InternalKotlinAnalysisPlugin>().sampleProviderFactory providing ::KotlinSampleProviderFactory
}

internal val descriptorSyntheticDocumentableDetector by extending {
plugin<InternalKotlinAnalysisPlugin>().syntheticDocumentableDetector providing { DescriptorSyntheticDocumentableDetector() }
}
Expand Down

0 comments on commit 9d7c372

Please sign in to comment.