aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/base/src/main/kotlin')
-rw-r--r--plugins/base/src/main/kotlin/DokkaBase.kt7
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt4
-rw-r--r--plugins/base/src/main/kotlin/transformers/pages/samples/DefaultSamplesTransformer.kt38
-rw-r--r--plugins/base/src/main/kotlin/transformers/pages/samples/KotlinWebsiteSamplesTransformer.kt196
-rw-r--r--plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt102
-rw-r--r--plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt3
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt3
7 files changed, 352 insertions, 1 deletions
diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt
index 1376cd8e..2be784ba 100644
--- a/plugins/base/src/main/kotlin/DokkaBase.kt
+++ b/plugins/base/src/main/kotlin/DokkaBase.kt
@@ -22,6 +22,8 @@ import org.jetbrains.dokka.base.transformers.pages.merger.FallbackPageMergerStra
import org.jetbrains.dokka.base.transformers.pages.merger.PageMerger
import org.jetbrains.dokka.base.transformers.pages.merger.PageMergerStrategy
import org.jetbrains.dokka.base.transformers.pages.merger.SameMethodNamePageMergerStrategy
+import org.jetbrains.dokka.base.transformers.pages.samples.DefaultSamplesTransformer
+import org.jetbrains.dokka.base.transformers.pages.samples.SamplesTransformer
import org.jetbrains.dokka.base.transformers.pages.sourcelinks.SourceLinksTransformer
import org.jetbrains.dokka.base.translators.descriptors.DefaultDescriptorToDocumentableTranslator
import org.jetbrains.dokka.base.translators.documentables.DefaultDocumentableToPageTranslator
@@ -37,6 +39,7 @@ class DokkaBase : DokkaPlugin() {
val externalLocationProviderFactory by extensionPoint<ExternalLocationProviderFactory>()
val outputWriter by extensionPoint<OutputWriter>()
val htmlPreprocessors by extensionPoint<PageTransformer>()
+ val samplesTransformer by extensionPoint<SamplesTransformer>()
val descriptorToDocumentableTranslator by extending(isFallback = true) {
CoreExtensions.descriptorToDocumentableTranslator providing ::DefaultDescriptorToDocumentableTranslator
@@ -128,6 +131,10 @@ class DokkaBase : DokkaPlugin() {
htmlPreprocessors with RootCreator
}
+ val defaultSamplesTransformer by extending(isFallback = true) {
+ samplesTransformer providing ::DefaultSamplesTransformer
+ }
+
val sourceLinksTransformer by extending {
htmlPreprocessors providing ::SourceLinksTransformer order { after(rootCreator) }
}
diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt
index c2960694..1365fecb 100644
--- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt
+++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt
@@ -10,6 +10,7 @@ import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.plugin
import org.jetbrains.dokka.plugability.query
+import org.jetbrains.dokka.plugability.querySingle
import java.io.File
open class HtmlRenderer(
@@ -18,7 +19,8 @@ open class HtmlRenderer(
private val pageList = mutableListOf<String>()
- override val preprocessors = context.plugin<DokkaBase>().query { htmlPreprocessors }
+ override val preprocessors = context.plugin<DokkaBase>().query { htmlPreprocessors } +
+ context.plugin<DokkaBase>().querySingle { samplesTransformer }
override fun FlowContent.wrapGroup(
node: ContentGroup,
diff --git a/plugins/base/src/main/kotlin/transformers/pages/samples/DefaultSamplesTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/samples/DefaultSamplesTransformer.kt
new file mode 100644
index 00000000..a391b534
--- /dev/null
+++ b/plugins/base/src/main/kotlin/transformers/pages/samples/DefaultSamplesTransformer.kt
@@ -0,0 +1,38 @@
+package org.jetbrains.dokka.base.transformers.pages.samples
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.kotlin.idea.kdoc.resolveKDocSampleLink
+import org.jetbrains.kotlin.psi.KtBlockExpression
+import org.jetbrains.kotlin.psi.KtDeclarationWithBody
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+
+class DefaultSamplesTransformer(context: DokkaContext) : SamplesTransformer(context) {
+
+ override 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() }.min() ?: 0
+ return lines.joinToString("\n") { it.drop(indent) }
+ }
+
+ private fun processSampleBody(psiElement: PsiElement): String = when (psiElement) {
+ is KtDeclarationWithBody -> {
+ val bodyExpression = psiElement.bodyExpression
+ when (bodyExpression) {
+ is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}")
+ else -> bodyExpression!!.text
+ }
+ }
+ else -> psiElement.text
+ }
+
+ override fun processImports(psiElement: PsiElement): String {
+ val psiFile = psiElement.containingFile
+ return when(val text = psiFile.safeAs<KtFile>()?.importList?.text) {
+ is String -> text
+ else -> ""
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/transformers/pages/samples/KotlinWebsiteSamplesTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/samples/KotlinWebsiteSamplesTransformer.kt
new file mode 100644
index 00000000..c099644f
--- /dev/null
+++ b/plugins/base/src/main/kotlin/transformers/pages/samples/KotlinWebsiteSamplesTransformer.kt
@@ -0,0 +1,196 @@
+package org.jetbrains.dokka.base.transformers.pages.samples
+
+import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiElementVisitor
+import com.intellij.psi.PsiWhiteSpace
+import com.intellij.psi.impl.source.tree.LeafPsiElement
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.allChildren
+import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
+import org.jetbrains.kotlin.psi.psiUtil.startOffset
+import org.jetbrains.kotlin.resolve.ImportPath
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import java.io.PrintWriter
+import java.io.StringWriter
+
+// TODO Inspect below class for any bugs. Big chunk of was ripped from 0.10.1
+class KotlinWebsiteSamplesTransformer(context: DokkaContext): SamplesTransformer(context) {
+
+ private class SampleBuilder : KtTreeVisitorVoid() {
+ val builder = StringBuilder()
+ val text: String
+ get() = builder.toString()
+
+ val errors = mutableListOf<ConvertError>()
+
+ data class ConvertError(val e: Exception, val text: String, val loc: String)
+
+ fun KtValueArgument.extractStringArgumentValue() =
+ (getArgumentExpression() as KtStringTemplateExpression)
+ .entries.joinToString("") { it.text }
+
+
+ fun convertAssertPrints(expression: KtCallExpression) {
+ val (argument, commentArgument) = expression.valueArguments
+ builder.apply {
+ append("println(")
+ append(argument.text)
+ append(") // ")
+ append(commentArgument.extractStringArgumentValue())
+ }
+ }
+
+ fun convertAssertTrueFalse(expression: KtCallExpression, expectedResult: Boolean) {
+ val (argument) = expression.valueArguments
+ builder.apply {
+ expression.valueArguments.getOrNull(1)?.let {
+ append("// ${it.extractStringArgumentValue()}")
+ val ws = expression.prevLeaf { it is PsiWhiteSpace }
+ append(ws?.text ?: "\n")
+ }
+ append("println(\"")
+ append(argument.text)
+ append(" is \${")
+ append(argument.text)
+ append("}\") // $expectedResult")
+ }
+ }
+
+ fun convertAssertFails(expression: KtCallExpression) {
+ val valueArguments = expression.valueArguments
+
+ val funcArgument: KtValueArgument
+ val message: KtValueArgument?
+
+ if (valueArguments.size == 1) {
+ message = null
+ funcArgument = valueArguments.first()
+ } else {
+ message = valueArguments.first()
+ funcArgument = valueArguments.last()
+ }
+
+ builder.apply {
+ val argument = funcArgument.extractFunctionalArgumentText()
+ append(argument.lines().joinToString(separator = "\n") { "// $it" })
+ append(" // ")
+ if (message != null) {
+ append(message.extractStringArgumentValue())
+ }
+ append(" will fail")
+ }
+ }
+
+ private fun KtValueArgument.extractFunctionalArgumentText(): String {
+ return if (getArgumentExpression() is KtLambdaExpression)
+ PsiTreeUtil.findChildOfType(this, KtBlockExpression::class.java)?.text ?: ""
+ else
+ text
+ }
+
+ fun convertAssertFailsWith(expression: KtCallExpression) {
+ val (funcArgument) = expression.valueArguments
+ val (exceptionType) = expression.typeArguments
+ builder.apply {
+ val argument = funcArgument.extractFunctionalArgumentText()
+ append(argument.lines().joinToString(separator = "\n") { "// $it" })
+ append(" // will fail with ")
+ append(exceptionType.text)
+ }
+ }
+
+ override fun visitCallExpression(expression: KtCallExpression) {
+ when (expression.calleeExpression?.text) {
+ "assertPrints" -> convertAssertPrints(expression)
+ "assertTrue" -> convertAssertTrueFalse(expression, expectedResult = true)
+ "assertFalse" -> convertAssertTrueFalse(expression, expectedResult = false)
+ "assertFails" -> convertAssertFails(expression)
+ "assertFailsWith" -> convertAssertFailsWith(expression)
+ else -> super.visitCallExpression(expression)
+ }
+ }
+
+ private fun reportProblemConvertingElement(element: PsiElement, e: Exception) {
+ val text = element.text
+ val document = PsiDocumentManager.getInstance(element.project).getDocument(element.containingFile)
+
+ val lineInfo = if (document != null) {
+ val lineNumber = document.getLineNumber(element.startOffset)
+ "$lineNumber, ${element.startOffset - document.getLineStartOffset(lineNumber)}"
+ } else {
+ "offset: ${element.startOffset}"
+ }
+ errors += ConvertError(e, text, lineInfo)
+ }
+
+ override fun visitElement(element: PsiElement) {
+ if (element is LeafPsiElement)
+ builder.append(element.text)
+
+ element.acceptChildren(object : PsiElementVisitor() {
+ override fun visitElement(element: PsiElement) {
+ try {
+ element.accept(this@SampleBuilder)
+ } catch (e: Exception) {
+ try {
+ reportProblemConvertingElement(element, e)
+ } finally {
+ builder.append(element.text) //recover
+ }
+ }
+ }
+ })
+ }
+
+ }
+
+ private fun PsiElement.buildSampleText(): String {
+ val sampleBuilder = SampleBuilder()
+ this.accept(sampleBuilder)
+
+ sampleBuilder.errors.forEach {
+ val sw = StringWriter()
+ val pw = PrintWriter(sw)
+ it.e.printStackTrace(pw)
+
+ this@KotlinWebsiteSamplesTransformer.context.logger.error("${containingFile.name}: (${it.loc}): Exception thrown while converting \n```\n${it.text}\n```\n$sw")
+ }
+ return sampleBuilder.text
+ }
+
+ val importsToIgnore = arrayOf("samples.*", "samples.Sample").map { ImportPath.fromString(it) }
+
+ override fun processImports(psiElement: PsiElement): String {
+ val psiFile = psiElement.containingFile
+ return when(val text = psiFile.safeAs<KtFile>()?.importList) {
+ is KtImportList -> text.let {
+ it.allChildren.filter {
+ it !is KtImportDirective || it.importPath !in importsToIgnore
+ }.joinToString(separator = "\n") { it.text }
+ }
+ else -> ""
+ }
+ }
+
+ override 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() }.min() ?: 0
+ return lines.joinToString("\n") { it.drop(indent) }
+ }
+
+ private fun processSampleBody(psiElement: PsiElement) = when (psiElement) {
+ is KtDeclarationWithBody -> {
+ val bodyExpression = psiElement.bodyExpression
+ val bodyExpressionText = bodyExpression!!.buildSampleText()
+ when (bodyExpression) {
+ is KtBlockExpression -> bodyExpressionText.removeSurrounding("{", "}")
+ else -> bodyExpressionText
+ }
+ }
+ else -> psiElement.buildSampleText()
+ }
+} \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt
new file mode 100644
index 00000000..a3a6b99c
--- /dev/null
+++ b/plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt
@@ -0,0 +1,102 @@
+package org.jetbrains.dokka.base.transformers.pages.samples
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.dokka.DokkaGenerator
+import org.jetbrains.dokka.EnvironmentAndFacade
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.AnalysisEnvironment
+import org.jetbrains.dokka.analysis.DokkaResolutionFacade
+import org.jetbrains.dokka.links.DRI
+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.transformers.pages.PageTransformer
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.utils.PathUtil
+import org.jetbrains.kotlin.idea.kdoc.resolveKDocSampleLink
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import java.io.File
+
+abstract class SamplesTransformer(val context: DokkaContext) : PageTransformer {
+
+ abstract fun processBody(psiElement: PsiElement): String
+ abstract fun processImports(psiElement: PsiElement): String
+
+ override fun invoke(input: RootPageNode): RootPageNode {
+
+ val analysis = setUpAnalysis(context)
+
+ return input.transformContentPagesTree { page ->
+ page.documentable?.documentation?.map?.entries?.fold(page) { acc, entry ->
+ entry.value.children.filterIsInstance<Sample>().fold(acc) { acc, sample ->
+ acc.modified(content = acc.content.addSample(page, entry.key, sample.name, analysis))
+ }
+ } ?: page
+ }
+ }
+
+ private fun setUpAnalysis(context: DokkaContext) = context.configuration.passesConfigurations.map {
+ it.platformData to AnalysisEnvironment(DokkaGenerator.DokkaMessageCollector(context.logger), it.analysisPlatform).run {
+ if (analysisPlatform == Platform.jvm) {
+ addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre())
+ }
+ it.classpath.forEach { addClasspath(File(it)) }
+
+ addSources(it.samples.map { it })
+
+ loadLanguageVersionSettings(it.languageVersion, it.apiVersion)
+
+ val environment = createCoreEnvironment()
+ val (facade, _) = createResolutionFacade(environment)
+ EnvironmentAndFacade(environment, facade)
+ }
+ }.toMap()
+
+ private fun ContentNode.addSample(contentPage: ContentPage, platform: PlatformData, fqName: String, analysis: Map<PlatformData, EnvironmentAndFacade>): ContentNode {
+ val facade = analysis[platform]?.facade ?:
+ return this.also { context.logger.warn("Cannot resolve facade for platform ${platform.name}")}
+ val psiElement = fqNameToPsiElement(facade, fqName) ?:
+ return this.also { context.logger.warn("Cannot find PsiElement corresponding to $fqName") }
+ val imports = processImports(psiElement) // TODO: Process somehow imports. Maybe just attach them at the top of each body
+ val body = processBody(psiElement)
+ val node = platformHintedContentCode(platform, contentPage.dri, body, "kotlin")
+ return this.safeAs<ContentGroup>()?.run { copy(
+ children = children.indexOfFirst { contentNode ->
+ contentNode.safeAs<ContentHeader>()?.children?.firstOrNull()?.safeAs<ContentText>()?.text == "Sample"
+ }.takeIf { it != -1 }?.let { children.apply { this.safeAs<MutableList<ContentNode>>()?.add(it+1, node) } } ?: children.also { context.logger.warn("Not found Sample block in ${contentPage.dri}")}
+ ) } ?: this.also { context.logger.warn("ContentPage ${contentPage.dri} cannot be cast to ContentGroup") }
+ }
+
+ private fun fqNameToPsiElement(resolutionFacade: DokkaResolutionFacade, functionName: String): PsiElement? {
+ val packageName = functionName.takeWhile { it != '.' }
+ val descriptor = resolutionFacade.resolveSession.getPackageFragment(FqName(packageName)) ?:
+ return null.also { context.logger.warn("Cannot find descriptor for package $packageName") }
+ val symbol = resolveKDocSampleLink(BindingContext.EMPTY, resolutionFacade, descriptor, functionName.split(".")).firstOrNull() ?:
+ return null.also { context.logger.warn("Unresolved function $functionName in @sample") }
+ return DescriptorToSourceUtils.descriptorToDeclaration(symbol)
+ }
+
+ private fun platformHintedContentCode(platformData: PlatformData, dri: Set<DRI>, content: String, language: String) =
+ PlatformHintedContent(
+ inner = ContentCode(
+ children = listOf(
+ ContentText(
+ text = content,
+ dci = DCI(dri, ContentKind.BriefComment),
+ platforms = setOf(platformData),
+ style = emptySet(),
+ extra = PropertyContainer.empty()
+ )
+ ),
+ language = language,
+ extra = PropertyContainer.empty(),
+ dci = DCI(dri, ContentKind.Source),
+ platforms = setOf(platformData),
+ style = emptySet()
+ ),
+ platforms = setOf(platformData)
+ )
+} \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt
index b19f83d3..7a93f0b9 100644
--- a/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt
+++ b/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt
@@ -2,6 +2,7 @@ package org.jetbrains.dokka.base.transformers.pages.sourcelinks
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.dokka.DokkaConfiguration
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DescriptorDocumentableSource
@@ -11,6 +12,8 @@ import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.transformers.pages.PageTransformer
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
+import org.jetbrains.kotlin.kdoc.psi.api.KDoc
+import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
index 8942991e..87f47ac8 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
@@ -7,6 +7,9 @@ import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.Doc
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.DFunction
+import org.jetbrains.dokka.model.doc.Property
+import org.jetbrains.dokka.model.doc.TagWrapper
import org.jetbrains.dokka.model.properties.WithExtraProperties
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.utilities.DokkaLogger