Skip to content

Commit

Permalink
Merge pull request #111 from Kotlin/website-samples
Browse files Browse the repository at this point in the history
Tweaked output for kotlin-website samples feature
  • Loading branch information
semoro authored Nov 29, 2016
2 parents edda34b + 875e7e5 commit 37cbd96
Show file tree
Hide file tree
Showing 17 changed files with 311 additions and 95 deletions.
2 changes: 2 additions & 0 deletions core/src/main/kotlin/Formats/FormatDescriptor.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jetbrains.dokka.Formats

import org.jetbrains.dokka.*
import org.jetbrains.dokka.Samples.SampleProcessingService
import kotlin.reflect.KClass

interface FormatDescriptor {
Expand All @@ -9,4 +10,5 @@ interface FormatDescriptor {
val generatorServiceClass: KClass<out Generator>
val packageDocumentationBuilderClass: KClass<out PackageDocumentationBuilder>
val javaDocumentationBuilderClass: KClass<out JavaDocumentationBuilder>
val sampleProcessingService: KClass<out SampleProcessingService>
}
31 changes: 22 additions & 9 deletions core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ class KotlinWebsiteOutputBuilder(to: StringBuilder,

override fun appendStrikethrough(body: () -> Unit) = wrapInTag("s", body)

private fun div(to: StringBuilder, cssClass: String, block: () -> Unit) {
to.append("<div class=\"$cssClass\">")
insideDiv++
private fun div(to: StringBuilder, cssClass: String, markdown: Boolean = false, block: () -> Unit) {
to.append("<div class=\"$cssClass\"")
if (markdown) to.append(" markdown=\"1\"")
to.append(">")
if (!markdown) insideDiv++
block()
insideDiv--
if (!markdown) insideDiv--
to.append("</div>\n")
}

Expand All @@ -51,12 +53,23 @@ class KotlinWebsiteOutputBuilder(to: StringBuilder,
}
}

override fun appendSampleBlockCode(language: String, imports: () -> Unit, body: () -> Unit) {
div(to, "sample", true) {
appendBlockCode(language) {
imports()
wrap("\nfun main(args: Array<String>) {", "}") {
wrap("\n//sampleStart\n", "\n//sampleEnd\n", body)
}
}
}
}

override fun appendAsOverloadGroup(to: StringBuilder, block: () -> Unit) {
to.append("<div class=\"overload-group\" markdown=\"1\">")
ensureParagraph()
block()
ensureParagraph()
to.append("</div>")
div(to, "overload-group", true) {
ensureParagraph()
block()
ensureParagraph()
}
}

override fun appendLink(href: String, body: () -> Unit) = wrap("<a href=\"$href\">", "</a>", body)
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/kotlin/Formats/StandardFormats.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.jetbrains.dokka.Formats

import org.jetbrains.dokka.*
import org.jetbrains.dokka.Samples.DefaultSampleProcessingService
import org.jetbrains.dokka.Samples.KotlinWebsiteSampleProcessingService
import org.jetbrains.dokka.Samples.SampleProcessingService
import kotlin.reflect.KClass

abstract class KotlinFormatDescriptorBase : FormatDescriptor {
Expand All @@ -9,6 +12,7 @@ abstract class KotlinFormatDescriptorBase : FormatDescriptor {

override val generatorServiceClass = FileGenerator::class
override val outlineServiceClass: KClass<out OutlineFormatService>? = null
override val sampleProcessingService: KClass<out SampleProcessingService> = DefaultSampleProcessingService::class
}

class HtmlFormatDescriptor : KotlinFormatDescriptorBase() {
Expand All @@ -22,11 +26,13 @@ class HtmlAsJavaFormatDescriptor : FormatDescriptor {
override val generatorServiceClass = FileGenerator::class
override val packageDocumentationBuilderClass = KotlinAsJavaDocumentationBuilder::class
override val javaDocumentationBuilderClass = JavaPsiDocumentationBuilder::class
override val sampleProcessingService: KClass<out SampleProcessingService> = DefaultSampleProcessingService::class
}

class KotlinWebsiteFormatDescriptor : KotlinFormatDescriptorBase() {
override val formatServiceClass = KotlinWebsiteFormatService::class
override val outlineServiceClass = YamlOutlineService::class
override val sampleProcessingService: KClass<out SampleProcessingService> = KotlinWebsiteSampleProcessingService::class
}

class JekyllFormatDescriptor : KotlinFormatDescriptorBase() {
Expand Down
20 changes: 15 additions & 5 deletions core/src/main/kotlin/Formats/StructuredFormatService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,

protected abstract fun ensureParagraph()

open fun appendSampleBlockCode(language: String, imports: () -> Unit, body: () -> Unit) = appendBlockCode(language, body)
abstract fun appendBlockCode(language: String, body: () -> Unit)
abstract fun appendHeader(level: Int = 1, body: () -> Unit)
abstract fun appendParagraph(body: () -> Unit)
Expand Down Expand Up @@ -140,13 +141,22 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
}

is ContentBlockCode -> appendBlockCode(content.language) {
for ((index, contentNode) in content.children.withIndex()) {
appendContent(contentNode)
if (index < content.children.size - 1) {
to.append("\n")
is ContentBlockSampleCode, is ContentBlockCode -> {
content as ContentBlockCode
fun ContentBlockCode.appendBlockCodeContent() {
for ((index, contentNode) in this.children.withIndex()) {
appendContent(contentNode)
if (index < this.children.size - 1) {
to.append("\n")
}
}
}
when (content) {
is ContentBlockSampleCode ->
appendSampleBlockCode(content.language, content.importsBlock::appendBlockCodeContent, { content.appendBlockCodeContent() })
is ContentBlockCode ->
appendBlockCode(content.language, { content.appendBlockCodeContent() })
}
}
is ContentHeading -> appendHeader(content.level) { appendContent(content.children) }
is ContentBlock -> appendContent(content.children)
Expand Down
83 changes: 8 additions & 75 deletions core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,20 @@ import com.intellij.psi.PsiDocCommentOwner
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.dokka.*
import org.jetbrains.dokka.Samples.SampleProcessingService
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.kdoc.findKDoc
import org.jetbrains.kotlin.idea.kdoc.getKDocLinkResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.ResolutionScope
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.resolve.source.PsiSourceElement

Expand All @@ -31,7 +27,8 @@ class DescriptorDocumentationParser
val logger: DokkaLogger,
val linkResolver: DeclarationLinkResolver,
val resolutionFacade: DokkaResolutionFacade,
val refGraph: NodeReferenceGraph)
val refGraph: NodeReferenceGraph,
val sampleService: SampleProcessingService)
{
fun parseDocumentation(descriptor: DeclarationDescriptor, inline: Boolean = false): Content =
parseDocumentationAndDetails(descriptor, inline).first
Expand Down Expand Up @@ -60,10 +57,10 @@ class DescriptorDocumentationParser
if (kdoc is KDocSection) {
val tags = kdoc.getTags()
tags.forEach {
when (it.name) {
"sample" ->
content.append(functionBody(descriptor, it.getSubjectName()))
"see" ->
when (it.knownTag) {
KDocKnownTag.SAMPLE ->
content.append(sampleService.resolveSample(descriptor, it.getSubjectName()))
KDocKnownTag.SEE ->
content.addTagToSeeAlso(descriptor, it)
else -> {
val section = content.addSection(javadocSectionDisplayName(it.name), it.getSubjectName())
Expand Down Expand Up @@ -144,68 +141,4 @@ class DescriptorDocumentationParser
}
}

private fun functionBody(descriptor: DeclarationDescriptor, functionName: String?): ContentNode {
if (functionName == null) {
logger.warn("Missing function name in @sample in ${descriptor.signature()}")
return ContentBlockCode().let() { it.append(ContentText("Missing function name in @sample")); it }
}
val scope = getKDocLinkResolutionScope(resolutionFacade, descriptor)
val rootPackage = resolutionFacade.moduleDescriptor.getPackage(FqName.ROOT)
val rootScope = rootPackage.memberScope
val symbol = resolveInScope(functionName, scope) ?: resolveInScope(functionName, rootScope)
if (symbol == null) {
logger.warn("Unresolved function $functionName in @sample in ${descriptor.signature()}")
return ContentBlockCode().let() { it.append(ContentText("Unresolved: $functionName")); it }
}
val psiElement = DescriptorToSourceUtils.descriptorToDeclaration(symbol)
if (psiElement == null) {
logger.warn("Can't find source for function $functionName in @sample in ${descriptor.signature()}")
return ContentBlockCode().let() { it.append(ContentText("Source not found: $functionName")); it }
}

val text = when (psiElement) {
is KtDeclarationWithBody -> ContentBlockCode().let() {
val bodyExpression = psiElement.bodyExpression
when (bodyExpression) {
is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}")
else -> bodyExpression!!.text
}
}
else -> psiElement.text
}

val lines = text.trimEnd().split("\n".toRegex()).toTypedArray().filterNot { it.length == 0 }
val indent = lines.map { it.takeWhile { it.isWhitespace() }.count() }.min() ?: 0
val finalText = lines.map { it.drop(indent) }.joinToString("\n")
return ContentBlockCode("kotlin").let() { it.append(ContentText(finalText)); it }
}

private fun resolveInScope(functionName: String, scope: ResolutionScope): DeclarationDescriptor? {
var currentScope = scope
val parts = functionName.split('.')

var symbol: DeclarationDescriptor? = null

for (part in parts) {
// short name
val symbolName = Name.identifier(part)
val partSymbol = currentScope.getContributedDescriptors(DescriptorKindFilter.ALL, { it == symbolName })
.filter { it.name == symbolName }
.firstOrNull()

if (partSymbol == null) {
symbol = null
break
}
currentScope = if (partSymbol is ClassDescriptor)
partSymbol.defaultType.memberScope
else if (partSymbol is PackageViewDescriptor)
partSymbol.memberScope
else
getKDocLinkResolutionScope(resolutionFacade, partSymbol)
symbol = partSymbol
}

return symbol
}
}
3 changes: 2 additions & 1 deletion core/src/main/kotlin/Model/Content.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ class ContentEmphasis() : ContentBlock()
class ContentStrong() : ContentBlock()
class ContentStrikethrough() : ContentBlock()
class ContentCode() : ContentBlock()
class ContentBlockCode(val language: String = "") : ContentBlock()
open class ContentBlockCode(val language: String = "") : ContentBlock()
class ContentBlockSampleCode(language: String = "kotlin", val importsBlock: ContentBlockCode = ContentBlockCode(language)) : ContentBlockCode(language)

abstract class ContentNodeLink() : ContentBlock() {
abstract val node: DocumentationNode?
Expand Down
106 changes: 106 additions & 0 deletions core/src/main/kotlin/Samples/DefaultSampleProcessingService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.jetbrains.dokka.Samples

import com.google.inject.Inject
import com.intellij.psi.PsiElement
import org.jetbrains.dokka.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.idea.kdoc.getKDocLinkResolutionScope
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.ResolutionScope


open class DefaultSampleProcessingService
@Inject constructor(val options: DocumentationOptions,
val logger: DokkaLogger,
val resolutionFacade: DokkaResolutionFacade)
: SampleProcessingService {

override fun resolveSample(descriptor: DeclarationDescriptor, functionName: String?): ContentNode {
if (functionName == null) {
logger.warn("Missing function name in @sample in ${descriptor.signature()}")
return ContentBlockSampleCode().apply { append(ContentText("//Missing function name in @sample")) }
}
val scope = getKDocLinkResolutionScope(resolutionFacade, descriptor)
val rootPackage = resolutionFacade.moduleDescriptor.getPackage(FqName.ROOT)
val rootScope = rootPackage.memberScope
val symbol = resolveInScope(functionName, scope) ?: resolveInScope(functionName, rootScope)
if (symbol == null) {
logger.warn("Unresolved function $functionName in @sample in ${descriptor.signature()}")
return ContentBlockSampleCode().apply { append(ContentText("//Unresolved: $functionName")) }
}
val psiElement = DescriptorToSourceUtils.descriptorToDeclaration(symbol)
if (psiElement == null) {
logger.warn("Can't find source for function $functionName in @sample in ${descriptor.signature()}")
return ContentBlockSampleCode().apply { append(ContentText("//Source not found: $functionName")) }
}

val text = processSampleBody(psiElement)

val lines = text.trimEnd().split("\n".toRegex()).toTypedArray().filterNot(String::isEmpty)
val indent = lines.map { it.takeWhile(Char::isWhitespace).count() }.min() ?: 0
val finalText = lines.map { it.drop(indent) }.joinToString("\n")

return ContentBlockSampleCode(importsBlock = processImports(psiElement)).apply { append(ContentText(finalText)) }
}

protected open 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
}

protected open fun processImports(psiElement: PsiElement): ContentBlockCode {
val psiFile = psiElement.containingFile
if (psiFile is KtFile) {
return ContentBlockCode("kotlin").apply {
append(ContentText(psiFile.importList?.text ?: ""))
}
} else {
return ContentBlockCode("")
}
}

private fun resolveInScope(functionName: String, scope: ResolutionScope): DeclarationDescriptor? {
var currentScope = scope
val parts = functionName.split('.')

var symbol: DeclarationDescriptor? = null

for (part in parts) {
// short name
val symbolName = Name.identifier(part)
val partSymbol = currentScope.getContributedDescriptors(DescriptorKindFilter.ALL, { it == symbolName })
.filter { it.name == symbolName }
.firstOrNull()

if (partSymbol == null) {
symbol = null
break
}
@Suppress("IfThenToElvis")
currentScope = if (partSymbol is ClassDescriptor)
partSymbol.defaultType.memberScope
else if (partSymbol is PackageViewDescriptor)
partSymbol.memberScope
else
getKDocLinkResolutionScope(resolutionFacade, partSymbol)
symbol = partSymbol
}

return symbol
}
}

Loading

0 comments on commit 37cbd96

Please sign in to comment.