From 9d7c3726d2359e09760ec45ca5e2138ec1b52cc2 Mon Sep 17 00:00:00 2001 From: Vadim Mishenev Date: Tue, 8 Aug 2023 17:15:51 +0300 Subject: [PATCH] Fix and refactor Sample Transformer (#3102) (cherry picked from commit 2fd8e9096706545f8b77e1e66bcc876d7e29f82c) --- plugins/base/src/main/kotlin/DokkaBase.kt | 16 +-- .../DefaultTemplateModelFactory.kt | 17 +-- .../pages/DefaultSamplesTransformer.kt | 113 ++++++++++++++++++ .../content/samples/ContentForSamplesTest.kt | 12 +- .../api/analysis-kotlin-api.api | 15 +++ .../internal/InternalKotlinAnalysisPlugin.kt | 2 + .../kotlin/internal/SampleProvider.kt | 30 +++++ .../compiler/api/compiler.api | 15 +++ .../CompilerDescriptorAnalysisPlugin.kt | 9 +- .../compiler/impl/KotlinSampleProvider.kt | 109 +++++++++++++++++ 10 files changed, 319 insertions(+), 19 deletions(-) create mode 100644 plugins/base/src/main/kotlin/transformers/pages/DefaultSamplesTransformer.kt create mode 100644 subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index 6da71b4bd8..9907d9b735 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -1,5 +1,3 @@ -@file:Suppress("unused") - package org.jetbrains.dokka.base import org.jetbrains.dokka.CoreExtensions @@ -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 @@ -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() @@ -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 } } @@ -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) } } @@ -270,11 +275,6 @@ class DokkaBase : DokkaPlugin() { val defaultKotlinAnalysis: org.jetbrains.dokka.plugability.Extension 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 - 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 diff --git a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt b/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt index aae2f65d24..4f4cdd7cca 100644 --- a/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt +++ b/plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt @@ -91,29 +91,30 @@ class DefaultTemplateModelFactory(val context: DokkaContext) : TemplateModelFact get() = URI(this).isAbsolute private fun Appendable.resourcesForPage(pathToRoot: String, resources: List): 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) } diff --git a/plugins/base/src/main/kotlin/transformers/pages/DefaultSamplesTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/DefaultSamplesTransformer.kt new file mode 100644 index 0000000000..0602bde20e --- /dev/null +++ b/plugins/base/src/main/kotlin/transformers/pages/DefaultSamplesTransformer.kt @@ -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().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().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) + 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, + dri: Set, + content: String, + language: String, + styles: Set