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:
+25
@@ -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"
|
||||
}
|
||||
|
||||
+196
@@ -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()
|
||||
}
|
||||
}
|
||||
+16
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.dokka.kotlinlang.SamplesTransformerPlugin
|
||||
+25
@@ -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"
|
||||
}
|
||||
|
||||
+21
@@ -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
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.dokka.kotlinlang.StdLibConfigurationPlugin
|
||||
@@ -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()
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package org.jetbrains.dokka.kotlinlang
|
||||
|
||||
import org.jetbrains.dokka.plugability.ConfigurableBlock
|
||||
|
||||
data class VersionFilterConfiguration(val targetVersion: String) : ConfigurableBlock
|
||||
+19
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+123
@@ -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) } }
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.dokka.kotlinlang.VersionFilterPlugin
|
||||
Reference in New Issue
Block a user