package org.jetbrains.dokka.Samples import com.google.inject.Inject import com.intellij.psi.PsiElement import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.dokka.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.allChildren import org.jetbrains.kotlin.psi.psiUtil.prevLeaf import org.jetbrains.kotlin.resolve.ImportPath open class KotlinWebsiteSampleProcessingService @Inject constructor(options: DocumentationOptions, logger: DokkaLogger, resolutionFacade: DokkaResolutionFacade) : DefaultSampleProcessingService(options, logger, resolutionFacade) { private class SampleBuilder : KtTreeVisitorVoid() { val builder = StringBuilder() val text: String get() = builder.toString() 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 (message, funcArgument) = expression.valueArguments builder.apply { val argument = if (funcArgument.getArgumentExpression() is KtLambdaExpression) PsiTreeUtil.findChildOfType(funcArgument, KtBlockExpression::class.java)?.text ?: "" else funcArgument.text append(argument.lines().joinToString(separator = "\n") { "// $it" }) append(" // ") append(message.extractStringArgumentValue()) append(" will fail") } } fun convertAssertFailsWith(expression: KtCallExpression) { val (funcArgument) = expression.valueArguments val (exceptionType) = expression.typeArguments builder.apply { val argument = if (funcArgument.firstChild is KtLambdaExpression) PsiTreeUtil.findChildOfType(funcArgument, KtBlockExpression::class.java)?.text ?: "" else funcArgument.text 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) } } override fun visitElement(element: PsiElement) { if (element is LeafPsiElement) builder.append(element.text) super.visitElement(element) } } private fun PsiElement.buildSampleText(): String { val sampleBuilder = SampleBuilder() this.accept(sampleBuilder) return sampleBuilder.text } val importsToIgnore = arrayOf("samples.*").map { ImportPath.fromString(it) } override fun processImports(psiElement: PsiElement): ContentBlockCode { val psiFile = psiElement.containingFile if (psiFile is KtFile) { return ContentBlockCode("kotlin").apply { append(ContentText("\n")) psiFile.importList?.let { it.allChildren.filter { it !is KtImportDirective || it.importPath !in importsToIgnore }.forEach { append(ContentText(it.text)) } } } } return super.processImports(psiElement) } override 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() } }