Setup project for docs build with new Dokka

- Update Dokka to 1.6.20-M1
- Update Dokka to 1.6.20
- Update Dokka to 1.7.0-dev
- Update Dokka to 1.7.20-dev-187

Co-authored-by: ilya.gorbunov@jetbrains.com
This commit is contained in:
vmishenev
2022-12-16 00:42:04 +01:00
committed by Space Team
parent 9bceb6cd0a
commit e94818a1cb
24 changed files with 1473 additions and 0 deletions
@@ -0,0 +1,25 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.6.10'
}
description "Dokka Plugin to transform the samples from stdlib"
repositories {
mavenCentral()
maven {
url 'https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev'
}
mavenLocal()
}
final String dokka_version = findProperty("dokka_version")
dependencies {
implementation "org.jetbrains.dokka:dokka-base:$dokka_version"
compileOnly "org.jetbrains.dokka:dokka-core:$dokka_version"
compileOnly "org.jetbrains.dokka:dokka-analysis:$dokka_version"
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
kotlinOptions.jvmTarget = "1.8"
}
@@ -0,0 +1,196 @@
package org.jetbrains.dokka.kotlinlang
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.base.transformers.pages.samples.SamplesTransformer
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
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() }.minOrNull() ?: 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()
}
}
@@ -0,0 +1,16 @@
package org.jetbrains.dokka.kotlinlang
import org.jetbrains.dokka.CoreExtensions
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.plugability.DokkaPlugin
class SamplesTransformerPlugin : DokkaPlugin() {
private val dokkaBase by lazy { plugin<DokkaBase>() }
@Suppress("unused")
val kotlinWebsiteSamplesTransformer by extending {
CoreExtensions.pageTransformer providing ::KotlinWebsiteSamplesTransformer override dokkaBase.defaultSamplesTransformer order {
before(dokkaBase.pageMerger)
}
}
}
@@ -0,0 +1,25 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.6.10'
}
description "Dokka Plugin to configure Dokka for stdlib"
repositories {
mavenCentral()
maven {
url 'https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev'
}
mavenLocal()
}
final String dokka_version = findProperty("dokka_version")
dependencies {
implementation "org.jetbrains.dokka:dokka-base:$dokka_version"
compileOnly "org.jetbrains.dokka:dokka-core:$dokka_version"
compileOnly "org.jetbrains.dokka:dokka-analysis:$dokka_version"
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
kotlinOptions.jvmTarget = "1.8"
}
@@ -0,0 +1,21 @@
package org.jetbrains.dokka.kotlinlang
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.plugability.DokkaPlugin
import org.jetbrains.dokka.analysis.DokkaAnalysisConfiguration
import org.jetbrains.dokka.analysis.KotlinAnalysis
class StdLibConfigurationPlugin : DokkaPlugin() {
private val dokkaBase by lazy { plugin<DokkaBase>() }
@Suppress("unused")
val stdLibKotlinAnalysis by extending {
dokkaBase.kotlinAnalysis providing { ctx ->
KotlinAnalysis(
sourceSets = ctx.configuration.sourceSets,
logger = ctx.logger,
analysisConfiguration = DokkaAnalysisConfiguration(ignoreCommonBuiltIns = true)
)
} override dokkaBase.defaultKotlinAnalysis
}
}
@@ -0,0 +1,27 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.6.10'
}
description "Dokka Plugin to filter version for stdlib"
repositories {
mavenCentral()
maven {
url 'https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev'
}
mavenLocal()
}
final String dokka_version = findProperty("dokka_version")
dependencies {
implementation "org.jetbrains.dokka:dokka-base:$dokka_version"
compileOnly "org.jetbrains.dokka:dokka-core:$dokka_version"
compileOnly "org.jetbrains.dokka:dokka-analysis:$dokka_version"
testImplementation 'org.jetbrains.kotlin:kotlin-test'}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
kotlinOptions.jvmTarget = "1.8"
}
test {
useJUnitPlatform()
}
@@ -0,0 +1,5 @@
package org.jetbrains.dokka.kotlinlang
import org.jetbrains.dokka.plugability.ConfigurableBlock
data class VersionFilterConfiguration(val targetVersion: String) : ConfigurableBlock
@@ -0,0 +1,19 @@
package org.jetbrains.dokka.kotlinlang
import org.jetbrains.dokka.CoreExtensions
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.plugability.DokkaPlugin
class VersionFilterPlugin : DokkaPlugin() {
private val dokkaBase by lazy { plugin<DokkaBase>() }
@Suppress("unused")
val versionFilter by extending {
CoreExtensions.documentableTransformer providing ::VersionFilterTransformer order {
after(dokkaBase.sinceKotlinTransformer)
before(dokkaBase.extensionsExtractor)
before(dokkaBase.inheritorsExtractor)
}
}
}
@@ -0,0 +1,123 @@
package org.jetbrains.dokka.kotlinlang
import org.jetbrains.dokka.DokkaConfiguration
import org.jetbrains.dokka.base.transformers.pages.annotations.SinceKotlinVersion
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.doc.CustomTagWrapper
import org.jetbrains.dokka.model.doc.Text
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.configuration
import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
class VersionFilterTransformer(private val dokkaContext: DokkaContext) :
DocumentableTransformer {
private val targetVersion =
configuration<VersionFilterPlugin, VersionFilterConfiguration>(dokkaContext)?.targetVersion?.let {
SinceKotlinVersion(
it
)
}
override fun invoke(original: DModule, context: DokkaContext): DModule = original.transform() as DModule
private fun <T : Documentable> T.transform(): Documentable? =
when (this) {
is DModule -> copy(
packages = packages.mapNotNull { it.transform() as DPackage? }
)
is DPackage -> copy(
classlikes = classlikes.mapNotNull { it.transform() as DClasslike? },
functions = functions.mapNotNull { it.transform() as DFunction? },
properties = properties.mapNotNull { it.transform() as DProperty? },
typealiases = typealiases.mapNotNull { it.transform() as DTypeAlias? }
).notEmpty()
is DClass -> filterSourceSets().ifNotEmpty {
this@transform.copy(
sourceSets = this,
classlikes = classlikes.mapNotNull { it.transform() as DClasslike? },
functions = functions.mapNotNull { it.transform() as DFunction? },
properties = properties.mapNotNull { it.transform() as DProperty? }
)
}
is DEnum -> filterSourceSets().ifNotEmpty {
this@transform.copy(
sourceSets = this,
classlikes = classlikes.mapNotNull { it.transform() as DClasslike? },
functions = functions.mapNotNull { it.transform() as DFunction? },
properties = properties.mapNotNull { it.transform() as DProperty? }
)
}
is DInterface -> filterSourceSets().ifNotEmpty {
this@transform.copy(
sourceSets = this,
classlikes = classlikes.mapNotNull { it.transform() as DClasslike? },
functions = functions.mapNotNull { it.transform() as DFunction? },
properties = properties.mapNotNull { it.transform() as DProperty? }
)
}
is DObject -> filterSourceSets().ifNotEmpty {
this@transform.copy(
sourceSets = this,
classlikes = classlikes.mapNotNull { it.transform() as DClasslike? },
functions = functions.mapNotNull { it.transform() as DFunction? },
properties = properties.mapNotNull { it.transform() as DProperty? }
)
}
is DTypeAlias -> filterSourceSets().ifNotEmpty {
this@transform.copy(
sourceSets = this,
)
}
is DAnnotation -> filterSourceSets().ifNotEmpty {
this@transform.copy(
sourceSets = this,
classlikes = classlikes.mapNotNull { it.transform() as DClasslike? },
functions = functions.mapNotNull { it.transform() as DFunction? },
properties = properties.mapNotNull { it.transform() as DProperty? }
)
}
is DFunction -> filterSourceSets().ifNotEmpty {
this@transform.copy(
sourceSets = this,
)
}
is DProperty -> filterSourceSets().ifNotEmpty {
this@transform.copy(
sourceSets = this,
)
}
is DParameter -> filterSourceSets().ifNotEmpty {
this@transform.copy(
sourceSets = this,
)
}
else -> this.also { dokkaContext.logger.warn("Unrecognized documentable $this while SinceKotlin transformation") }
}
private fun DPackage.notEmpty() =
this.takeUnless { classlikes.isEmpty() && functions.isEmpty() && properties.isEmpty() }
private fun Documentable.filterSourceSets(): Set<DokkaConfiguration.DokkaSourceSet> = this.sourceSets.filter {
val currentVersion = getVersionFromCustomTag(it)
targetVersion == null || currentVersion == null || currentVersion <= targetVersion
}.toSet()
private fun Documentable.getVersionFromCustomTag(sourceSet: DokkaConfiguration.DokkaSourceSet): SinceKotlinVersion? {
return documentation[sourceSet]?.children?.find { it is CustomTagWrapper && it.name == "Since Kotlin" }
?.let { (it.children[0] as? Text)?.body?.let { txt -> SinceKotlinVersion(txt) } }
}
}