1 package org.jetbrains.dokka.Samples 2 3 import com.google.inject.Inject 4 import com.intellij.psi.PsiElement 5 import org.jetbrains.dokka.* 6 import org.jetbrains.kotlin.descriptors.ClassDescriptor 7 import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 8 import org.jetbrains.kotlin.descriptors.PackageViewDescriptor 9 import org.jetbrains.kotlin.idea.kdoc.getKDocLinkResolutionScope 10 import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink 11 import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag 12 import org.jetbrains.kotlin.name.Name 13 import org.jetbrains.kotlin.psi.KtBlockExpression 14 import org.jetbrains.kotlin.psi.KtDeclarationWithBody 15 import org.jetbrains.kotlin.psi.KtFile 16 import org.jetbrains.kotlin.resolve.BindingContext 17 import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils 18 import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 19 import org.jetbrains.kotlin.resolve.scopes.ResolutionScope 20 21 22 open class DefaultSampleProcessingService 23 @Inject constructor(val options: DocumentationOptions, 24 val logger: DokkaLogger, 25 val resolutionFacade: DokkaResolutionFacade) 26 : SampleProcessingService { 27 resolveSamplenull28 override fun resolveSample(descriptor: DeclarationDescriptor, functionName: String?, kdocTag: KDocTag): ContentNode { 29 if (functionName == null) { 30 logger.warn("Missing function name in @sample in ${descriptor.signature()}") 31 return ContentBlockSampleCode().apply { append(ContentText("//Missing function name in @sample")) } 32 } 33 val bindingContext = BindingContext.EMPTY 34 val symbol = resolveKDocLink(bindingContext, resolutionFacade, descriptor, kdocTag, functionName.split(".")).firstOrNull() 35 if (symbol == null) { 36 logger.warn("Unresolved function $functionName in @sample in ${descriptor.signature()}") 37 return ContentBlockSampleCode().apply { append(ContentText("//Unresolved: $functionName")) } 38 } 39 val psiElement = DescriptorToSourceUtils.descriptorToDeclaration(symbol) 40 if (psiElement == null) { 41 logger.warn("Can't find source for function $functionName in @sample in ${descriptor.signature()}") 42 return ContentBlockSampleCode().apply { append(ContentText("//Source not found: $functionName")) } 43 } 44 45 val text = processSampleBody(psiElement).trim { it == '\n' || it == '\r' }.trimEnd() 46 val lines = text.split("\n") 47 val indent = lines.filter(String::isNotBlank).map { it.takeWhile(Char::isWhitespace).count() }.min() ?: 0 48 val finalText = lines.map { it.drop(indent) }.joinToString("\n") 49 50 return ContentBlockSampleCode(importsBlock = processImports(psiElement)).apply { append(ContentText(finalText)) } 51 } 52 processSampleBodynull53 protected open fun processSampleBody(psiElement: PsiElement): String = when (psiElement) { 54 is KtDeclarationWithBody -> { 55 val bodyExpression = psiElement.bodyExpression 56 when (bodyExpression) { 57 is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}") 58 else -> bodyExpression!!.text 59 } 60 } 61 else -> psiElement.text 62 } 63 processImportsnull64 protected open fun processImports(psiElement: PsiElement): ContentBlockCode { 65 val psiFile = psiElement.containingFile 66 if (psiFile is KtFile) { 67 return ContentBlockCode("kotlin").apply { 68 append(ContentText(psiFile.importList?.text ?: "")) 69 } 70 } else { 71 return ContentBlockCode("") 72 } 73 } 74 resolveInScopenull75 private fun resolveInScope(functionName: String, scope: ResolutionScope): DeclarationDescriptor? { 76 var currentScope = scope 77 val parts = functionName.split('.') 78 79 var symbol: DeclarationDescriptor? = null 80 81 for (part in parts) { 82 // short name 83 val symbolName = Name.identifier(part) 84 val partSymbol = currentScope.getContributedDescriptors(DescriptorKindFilter.ALL, { it == symbolName }) 85 .filter { it.name == symbolName } 86 .firstOrNull() 87 88 if (partSymbol == null) { 89 symbol = null 90 break 91 } 92 @Suppress("IfThenToElvis") 93 currentScope = if (partSymbol is ClassDescriptor) 94 partSymbol.defaultType.memberScope 95 else if (partSymbol is PackageViewDescriptor) 96 partSymbol.memberScope 97 else 98 getKDocLinkResolutionScope(resolutionFacade, partSymbol) 99 symbol = partSymbol 100 } 101 102 return symbol 103 } 104 } 105 106