aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/Kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/kotlin/Kotlin')
-rw-r--r--core/src/main/kotlin/Kotlin/ContentBuilder.kt132
-rw-r--r--core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt43
-rw-r--r--core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt199
-rw-r--r--core/src/main/kotlin/Kotlin/DocumentationBuilder.kt653
-rw-r--r--core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt64
-rw-r--r--core/src/main/kotlin/Kotlin/KotlinLanguageService.kt409
6 files changed, 1500 insertions, 0 deletions
diff --git a/core/src/main/kotlin/Kotlin/ContentBuilder.kt b/core/src/main/kotlin/Kotlin/ContentBuilder.kt
new file mode 100644
index 00000000..c4bb18de
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/ContentBuilder.kt
@@ -0,0 +1,132 @@
+package org.jetbrains.dokka
+
+import org.intellij.markdown.MarkdownElementTypes
+import org.intellij.markdown.MarkdownTokenTypes
+import org.intellij.markdown.html.entities.EntityConverter
+import java.util.*
+
+public fun buildContent(tree: MarkdownNode, linkResolver: (String) -> ContentBlock, inline: Boolean = false): MutableContent {
+ val result = MutableContent()
+ if (inline) {
+ buildInlineContentTo(tree, result, linkResolver)
+ }
+ else {
+ buildContentTo(tree, result, linkResolver)
+ }
+ return result
+}
+
+public fun buildContentTo(tree: MarkdownNode, target: ContentBlock, linkResolver: (String) -> ContentBlock) {
+// println(tree.toTestString())
+ val nodeStack = ArrayDeque<ContentBlock>()
+ nodeStack.push(target)
+
+ tree.visit {node, processChildren ->
+ val parent = nodeStack.peek()
+
+ fun appendNodeWithChildren(content: ContentBlock) {
+ nodeStack.push(content)
+ processChildren()
+ parent.append(nodeStack.pop())
+ }
+
+ when (node.type) {
+ MarkdownElementTypes.ATX_1 -> appendNodeWithChildren(ContentHeading(1))
+ MarkdownElementTypes.ATX_2 -> appendNodeWithChildren(ContentHeading(2))
+ MarkdownElementTypes.ATX_3 -> appendNodeWithChildren(ContentHeading(3))
+ MarkdownElementTypes.ATX_4 -> appendNodeWithChildren(ContentHeading(4))
+ MarkdownElementTypes.ATX_5 -> appendNodeWithChildren(ContentHeading(5))
+ MarkdownElementTypes.ATX_6 -> appendNodeWithChildren(ContentHeading(6))
+ MarkdownElementTypes.UNORDERED_LIST -> appendNodeWithChildren(ContentUnorderedList())
+ MarkdownElementTypes.ORDERED_LIST -> appendNodeWithChildren(ContentOrderedList())
+ MarkdownElementTypes.LIST_ITEM -> appendNodeWithChildren(ContentListItem())
+ MarkdownElementTypes.EMPH -> appendNodeWithChildren(ContentEmphasis())
+ MarkdownElementTypes.STRONG -> appendNodeWithChildren(ContentStrong())
+ MarkdownElementTypes.CODE_SPAN -> appendNodeWithChildren(ContentCode())
+ MarkdownElementTypes.CODE_BLOCK,
+ MarkdownElementTypes.CODE_FENCE -> appendNodeWithChildren(ContentBlockCode())
+ MarkdownElementTypes.PARAGRAPH -> appendNodeWithChildren(ContentParagraph())
+
+ MarkdownElementTypes.INLINE_LINK -> {
+ val label = node.child(MarkdownElementTypes.LINK_TEXT)?.child(MarkdownTokenTypes.TEXT)
+ val destination = node.child(MarkdownElementTypes.LINK_DESTINATION)
+ if (label != null) {
+ if (destination != null) {
+ val link = ContentExternalLink(destination.text)
+ link.append(ContentText(label.text))
+ parent.append(link)
+ } else {
+ val link = ContentExternalLink(label.text)
+ link.append(ContentText(label.text))
+ parent.append(link)
+ }
+ }
+ }
+ MarkdownElementTypes.SHORT_REFERENCE_LINK,
+ MarkdownElementTypes.FULL_REFERENCE_LINK -> {
+ val label = node.child(MarkdownElementTypes.LINK_LABEL)?.child(MarkdownTokenTypes.TEXT)
+ if (label != null) {
+ val link = linkResolver(label.text)
+ val linkText = node.child(MarkdownElementTypes.LINK_TEXT)?.child(MarkdownTokenTypes.TEXT)
+ link.append(ContentText(linkText?.text ?: label.text))
+ parent.append(link)
+ }
+ }
+ MarkdownTokenTypes.WHITE_SPACE,
+ MarkdownTokenTypes.EOL -> {
+ if (keepWhitespace(nodeStack.peek()) && node.parent?.children?.last() != node) {
+ parent.append(ContentText(node.text))
+ }
+ }
+
+ MarkdownTokenTypes.CODE -> {
+ val block = ContentBlockCode()
+ block.append(ContentText(node.text))
+ parent.append(block)
+ }
+
+ MarkdownTokenTypes.TEXT -> {
+ fun createEntityOrText(text: String): ContentNode {
+ if (text == "&amp;" || text == "&quot;" || text == "&lt;" || text == "&gt;") {
+ return ContentEntity(text)
+ }
+ if (text == "&") {
+ return ContentEntity("&amp;")
+ }
+ val decodedText = EntityConverter.replaceEntities(text, true, true)
+ if (decodedText != text) {
+ return ContentEntity(text)
+ }
+ return ContentText(text)
+ }
+
+ parent.append(createEntityOrText(node.text))
+ }
+
+ MarkdownTokenTypes.COLON,
+ MarkdownTokenTypes.DOUBLE_QUOTE,
+ MarkdownTokenTypes.LT,
+ MarkdownTokenTypes.GT,
+ MarkdownTokenTypes.LPAREN,
+ MarkdownTokenTypes.RPAREN,
+ MarkdownTokenTypes.LBRACKET,
+ MarkdownTokenTypes.RBRACKET,
+ MarkdownTokenTypes.CODE_FENCE_CONTENT -> {
+ parent.append(ContentText(node.text))
+ }
+ else -> {
+ processChildren()
+ }
+ }
+ }
+}
+
+private fun keepWhitespace(node: ContentNode) = node is ContentParagraph || node is ContentSection
+
+public fun buildInlineContentTo(tree: MarkdownNode, target: ContentBlock, linkResolver: (String) -> ContentBlock) {
+ val inlineContent = tree.children.singleOrNull { it.type == MarkdownElementTypes.PARAGRAPH }?.children ?: listOf(tree)
+ inlineContent.forEach {
+ buildContentTo(it, target, linkResolver)
+ }
+}
+
diff --git a/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt
new file mode 100644
index 00000000..2569bc71
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt
@@ -0,0 +1,43 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
+
+class DeclarationLinkResolver
+ @Inject constructor(val resolutionFacade: DokkaResolutionFacade,
+ val refGraph: NodeReferenceGraph,
+ val logger: DokkaLogger) {
+ fun resolveContentLink(fromDescriptor: DeclarationDescriptor, href: String): ContentBlock {
+ val symbol = try {
+ val symbols = resolveKDocLink(resolutionFacade, fromDescriptor, null, href.split('.').toList())
+ findTargetSymbol(symbols)
+ } catch(e: Exception) {
+ null
+ }
+
+ // don't include unresolved links in generated doc
+ // assume that if an href doesn't contain '/', it's not an attempt to reference an external file
+ if (symbol != null) {
+ return ContentNodeLazyLink(href, { -> refGraph.lookup(symbol.signature()) })
+ }
+ if ("/" in href) {
+ return ContentExternalLink(href)
+ }
+ logger.warn("Unresolved link to $href in doc comment of ${fromDescriptor.signatureWithSourceLocation()}")
+ return ContentExternalLink("#")
+ }
+
+ fun findTargetSymbol(symbols: Collection<DeclarationDescriptor>): DeclarationDescriptor? {
+ if (symbols.isEmpty()) {
+ return null
+ }
+ val symbol = symbols.first()
+ if (symbol is CallableMemberDescriptor && symbol.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) {
+ return symbol.overriddenDescriptors.firstOrNull()
+ }
+ return symbol
+ }
+
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt b/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt
new file mode 100644
index 00000000..b7705ec9
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt
@@ -0,0 +1,199 @@
+package org.jetbrains.dokka.Kotlin
+
+import com.google.inject.Inject
+import com.intellij.psi.PsiDocCommentOwner
+import com.intellij.psi.PsiNamedElement
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.dokka.*
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.idea.kdoc.KDocFinder
+import org.jetbrains.kotlin.idea.kdoc.getResolutionScope
+import org.jetbrains.kotlin.incremental.components.NoLookupLocation
+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.scopes.DescriptorKindFilter
+import org.jetbrains.kotlin.resolve.scopes.ResolutionScope
+import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
+import org.jetbrains.kotlin.resolve.source.PsiSourceElement
+
+class DescriptorDocumentationParser
+ @Inject constructor(val options: DocumentationOptions,
+ val logger: DokkaLogger,
+ val linkResolver: DeclarationLinkResolver,
+ val resolutionFacade: DokkaResolutionFacade,
+ val refGraph: NodeReferenceGraph)
+{
+ fun parseDocumentation(descriptor: DeclarationDescriptor, inline: Boolean = false): Content =
+ parseDocumentationAndDetails(descriptor, inline).first
+
+ fun parseDocumentationAndDetails(descriptor: DeclarationDescriptor, inline: Boolean = false): Pair<Content, (DocumentationNode) -> Unit> {
+ if (descriptor is JavaClassDescriptor || descriptor is JavaCallableMemberDescriptor) {
+ return parseJavadoc(descriptor)
+ }
+
+ val kdoc = KDocFinder.findKDoc(descriptor) ?: findStdlibKDoc(descriptor)
+ if (kdoc == null) {
+ if (options.reportUndocumented && !descriptor.isDeprecated() &&
+ descriptor !is ValueParameterDescriptor && descriptor !is TypeParameterDescriptor &&
+ descriptor !is PropertyAccessorDescriptor) {
+ logger.warn("No documentation for ${descriptor.signatureWithSourceLocation()}")
+ }
+ return Content.Empty to { node -> }
+ }
+ var kdocText = kdoc.getContent()
+ // workaround for code fence parsing problem in IJ markdown parser
+ if (kdocText.endsWith("```") || kdocText.endsWith("~~~")) {
+ kdocText += "\n"
+ }
+ val tree = parseMarkdown(kdocText)
+ val content = buildContent(tree, { href -> linkResolver.resolveContentLink(descriptor, href) }, inline)
+ if (kdoc is KDocSection) {
+ val tags = kdoc.getTags()
+ tags.forEach {
+ when (it.name) {
+ "sample" ->
+ content.append(functionBody(descriptor, it.getSubjectName()))
+ "see" ->
+ content.addTagToSeeAlso(descriptor, it)
+ else -> {
+ val section = content.addSection(javadocSectionDisplayName(it.name), it.getSubjectName())
+ val sectionContent = it.getContent()
+ val markdownNode = parseMarkdown(sectionContent)
+ buildInlineContentTo(markdownNode, section, { href -> linkResolver.resolveContentLink(descriptor, href) })
+ }
+ }
+ }
+ }
+ return content to { node -> }
+ }
+
+ /**
+ * Special case for generating stdlib documentation (the Any class to which the override chain will resolve
+ * is not the same one as the Any class included in the source scope).
+ */
+ fun findStdlibKDoc(descriptor: DeclarationDescriptor): KDocTag? {
+ if (descriptor !is CallableMemberDescriptor) {
+ return null
+ }
+ val name = descriptor.name.asString()
+ if (name == "equals" || name == "hashCode" || name == "toString") {
+ var deepestDescriptor: CallableMemberDescriptor = descriptor
+ while (!deepestDescriptor.overriddenDescriptors.isEmpty()) {
+ deepestDescriptor = deepestDescriptor.overriddenDescriptors.first()
+ }
+ if (DescriptorUtils.getFqName(deepestDescriptor.containingDeclaration).asString() == "kotlin.Any") {
+ val anyClassDescriptors = resolutionFacade.resolveSession.getTopLevelClassDescriptors(
+ FqName.fromSegments(listOf("kotlin", "Any")), NoLookupLocation.FROM_IDE)
+ anyClassDescriptors.forEach {
+ val anyMethod = it.getMemberScope(listOf())
+ .getDescriptorsFiltered(DescriptorKindFilter.FUNCTIONS, { it == descriptor.name })
+ .single()
+ val kdoc = KDocFinder.findKDoc(anyMethod)
+ if (kdoc != null) {
+ return kdoc
+ }
+ }
+ }
+ }
+ return null
+ }
+
+ fun parseJavadoc(descriptor: DeclarationDescriptor): Pair<Content, (DocumentationNode) -> Unit> {
+ val psi = ((descriptor as? DeclarationDescriptorWithSource)?.source as? PsiSourceElement)?.psi
+ if (psi is PsiDocCommentOwner) {
+ val parseResult = JavadocParser(refGraph).parseDocumentation(psi as PsiNamedElement)
+ return parseResult.content to { node ->
+ parseResult.deprecatedContent?.let {
+ val deprecationNode = DocumentationNode("", it, DocumentationNode.Kind.Modifier)
+ node.append(deprecationNode, DocumentationReference.Kind.Deprecation)
+ }
+ }
+ }
+ return Content.Empty to { node -> }
+ }
+
+ fun KDocSection.getTags(): Array<KDocTag> = PsiTreeUtil.getChildrenOfType(this, KDocTag::class.java) ?: arrayOf()
+
+ private fun MutableContent.addTagToSeeAlso(descriptor: DeclarationDescriptor, seeTag: KDocTag) {
+ val subjectName = seeTag.getSubjectName()
+ if (subjectName != null) {
+ val seeSection = findSectionByTag("See Also") ?: addSection("See Also", null)
+ val link = linkResolver.resolveContentLink(descriptor, subjectName)
+ link.append(ContentText(subjectName))
+ val para = ContentParagraph()
+ para.append(link)
+ seeSection.append(para)
+ }
+ }
+
+ 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 = getResolutionScope(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.guess(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
+ getResolutionScope(resolutionFacade, partSymbol)
+ symbol = partSymbol
+ }
+
+ return symbol
+ }
+}
diff --git a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
new file mode 100644
index 00000000..6551ded6
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
@@ -0,0 +1,653 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.psi.PsiJavaFile
+import org.jetbrains.dokka.DocumentationNode.Kind
+import org.jetbrains.dokka.Kotlin.DescriptorDocumentationParser
+import org.jetbrains.kotlin.builtins.KotlinBuiltIns
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.descriptors.annotations.Annotated
+import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
+import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor
+import org.jetbrains.kotlin.idea.caches.resolve.KotlinCacheService
+import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo
+import org.jetbrains.kotlin.idea.kdoc.KDocFinder
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.KtModifierListOwner
+import org.jetbrains.kotlin.psi.KtParameter
+import org.jetbrains.kotlin.resolve.DescriptorUtils
+import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
+import org.jetbrains.kotlin.resolve.constants.ConstantValue
+import org.jetbrains.kotlin.resolve.constants.TypedCompileTimeConstant
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.kotlin.resolve.descriptorUtil.isDocumentedAnnotation
+import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
+import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
+import org.jetbrains.kotlin.resolve.source.PsiSourceElement
+import org.jetbrains.kotlin.resolve.source.getPsi
+import org.jetbrains.kotlin.types.ErrorUtils
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.types.TypeProjection
+
+public data class DocumentationOptions(val outputDir: String,
+ val outputFormat: String,
+ val includeNonPublic: Boolean = false,
+ val reportUndocumented: Boolean = true,
+ val skipEmptyPackages: Boolean = true,
+ val skipDeprecated: Boolean = false,
+ val sourceLinks: List<SourceLinkDefinition>)
+
+private fun isSamePackage(descriptor1: DeclarationDescriptor, descriptor2: DeclarationDescriptor): Boolean {
+ val package1 = DescriptorUtils.getParentOfType(descriptor1, PackageFragmentDescriptor::class.java)
+ val package2 = DescriptorUtils.getParentOfType(descriptor2, PackageFragmentDescriptor::class.java)
+ return package1 != null && package2 != null && package1.fqName == package2.fqName
+}
+
+interface PackageDocumentationBuilder {
+ fun buildPackageDocumentation(documentationBuilder: DocumentationBuilder,
+ packageName: FqName,
+ packageNode: DocumentationNode,
+ declarations: List<DeclarationDescriptor>)
+}
+
+class DocumentationBuilder
+ @Inject constructor(val resolutionFacade: DokkaResolutionFacade,
+ val descriptorDocumentationParser: DescriptorDocumentationParser,
+ val options: DocumentationOptions,
+ val refGraph: NodeReferenceGraph,
+ val logger: DokkaLogger)
+{
+ val visibleToDocumentation = setOf(Visibilities.PROTECTED, Visibilities.PUBLIC)
+ val boringBuiltinClasses = setOf(
+ "kotlin.Unit", "kotlin.Byte", "kotlin.Short", "kotlin.Int", "kotlin.Long", "kotlin.Char", "kotlin.Boolean",
+ "kotlin.Float", "kotlin.Double", "kotlin.String", "kotlin.Array", "kotlin.Any")
+ val knownModifiers = setOf(
+ KtTokens.PUBLIC_KEYWORD, KtTokens.PROTECTED_KEYWORD, KtTokens.INTERNAL_KEYWORD, KtTokens.PRIVATE_KEYWORD,
+ KtTokens.OPEN_KEYWORD, KtTokens.FINAL_KEYWORD, KtTokens.ABSTRACT_KEYWORD, KtTokens.SEALED_KEYWORD,
+ KtTokens.OVERRIDE_KEYWORD)
+
+ fun link(node: DocumentationNode, descriptor: DeclarationDescriptor, kind: DocumentationReference.Kind) {
+ refGraph.link(node, descriptor.signature(), kind)
+ }
+
+ fun link(fromDescriptor: DeclarationDescriptor?, toDescriptor: DeclarationDescriptor?, kind: DocumentationReference.Kind) {
+ if (fromDescriptor != null && toDescriptor != null) {
+ refGraph.link(fromDescriptor.signature(), toDescriptor.signature(), kind)
+ }
+ }
+
+ fun register(descriptor: DeclarationDescriptor, node: DocumentationNode) {
+ refGraph.register(descriptor.signature(), node)
+ }
+
+ fun <T> nodeForDescriptor(descriptor: T, kind: Kind): DocumentationNode where T : DeclarationDescriptor, T : Named {
+ val (doc, callback) = descriptorDocumentationParser.parseDocumentationAndDetails(descriptor, kind == Kind.Parameter)
+ val node = DocumentationNode(descriptor.name.asString(), doc, kind).withModifiers(descriptor)
+ callback(node)
+ return node
+ }
+
+ private fun DocumentationNode.withModifiers(descriptor: DeclarationDescriptor) : DocumentationNode{
+ if (descriptor is MemberDescriptor) {
+ appendVisibility(descriptor)
+ if (descriptor !is ConstructorDescriptor) {
+ appendModality(descriptor)
+ }
+ }
+ return this
+ }
+
+ fun DocumentationNode.appendModality(descriptor: MemberDescriptor) {
+ var modality = descriptor.modality
+ if (modality == Modality.OPEN) {
+ val containingClass = descriptor.containingDeclaration as? ClassDescriptor
+ if (containingClass?.modality == Modality.FINAL) {
+ modality = Modality.FINAL
+ }
+ }
+ val modifier = modality.name.toLowerCase()
+ appendTextNode(modifier, DocumentationNode.Kind.Modifier)
+ }
+
+ fun DocumentationNode.appendVisibility(descriptor: DeclarationDescriptorWithVisibility) {
+ val modifier = descriptor.visibility.normalize().displayName
+ appendTextNode(modifier, DocumentationNode.Kind.Modifier)
+ }
+
+ fun DocumentationNode.appendSupertypes(descriptor: ClassDescriptor) {
+ val superTypes = descriptor.typeConstructor.supertypes
+ for (superType in superTypes) {
+ if (!ignoreSupertype(superType)) {
+ appendType(superType, DocumentationNode.Kind.Supertype)
+ val superclass = superType?.constructor?.declarationDescriptor
+ link(superclass, descriptor, DocumentationReference.Kind.Inheritor)
+ link(descriptor, superclass, DocumentationReference.Kind.Superclass)
+ }
+ }
+ }
+
+ private fun ignoreSupertype(superType: KotlinType): Boolean {
+ val superClass = superType.constructor.declarationDescriptor as? ClassDescriptor
+ if (superClass != null) {
+ val fqName = DescriptorUtils.getFqNameSafe(superClass).asString()
+ return fqName == "kotlin.Annotation" || fqName == "kotlin.Enum" || fqName == "kotlin.Any"
+ }
+ return false
+ }
+
+ fun DocumentationNode.appendProjection(projection: TypeProjection, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type) {
+ if (projection.isStarProjection) {
+ appendTextNode("*", Kind.Type)
+ }
+ else {
+ appendType(projection.type, kind, projection.projectionKind.label)
+ }
+ }
+
+ fun DocumentationNode.appendType(kotlinType: KotlinType?, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type, prefix: String = "") {
+ if (kotlinType == null)
+ return
+ val classifierDescriptor = kotlinType.constructor.declarationDescriptor
+ val name = when (classifierDescriptor) {
+ is ClassDescriptor -> {
+ if (classifierDescriptor.isCompanionObject) {
+ classifierDescriptor.containingDeclaration.name.asString() +
+ "." + classifierDescriptor.name.asString()
+ }
+ else {
+ classifierDescriptor.name.asString()
+ }
+ }
+ is Named -> classifierDescriptor.name.asString()
+ else -> "<anonymous>"
+ }
+ val node = DocumentationNode(name, Content.Empty, kind)
+ if (prefix != "") {
+ node.appendTextNode(prefix, Kind.Modifier)
+ }
+ if (kotlinType.isMarkedNullable) {
+ node.appendTextNode("?", Kind.NullabilityModifier)
+ }
+ if (classifierDescriptor != null) {
+ link(node, classifierDescriptor,
+ if (classifierDescriptor.isBoringBuiltinClass()) DocumentationReference.Kind.HiddenLink else DocumentationReference.Kind.Link)
+ }
+
+ append(node, DocumentationReference.Kind.Detail)
+ node.appendAnnotations(kotlinType)
+ for (typeArgument in kotlinType.arguments) {
+ node.appendProjection(typeArgument)
+ }
+ }
+
+ fun ClassifierDescriptor.isBoringBuiltinClass(): Boolean =
+ DescriptorUtils.getFqName(this).asString() in boringBuiltinClasses
+
+ fun DocumentationNode.appendAnnotations(annotated: Annotated) {
+ annotated.annotations.filter { it.isDocumented() }.forEach {
+ val annotationNode = it.build()
+ if (annotationNode != null) {
+ append(annotationNode,
+ if (annotationNode.isDeprecation()) DocumentationReference.Kind.Deprecation else DocumentationReference.Kind.Annotation)
+ }
+ }
+ }
+
+ fun DocumentationNode.appendModifiers(descriptor: DeclarationDescriptor) {
+ val psi = (descriptor as DeclarationDescriptorWithSource).source.getPsi() as? KtModifierListOwner ?: return
+ KtTokens.MODIFIER_KEYWORDS_ARRAY.filter { it !in knownModifiers }.forEach {
+ if (psi.hasModifier(it)) {
+ appendTextNode(it.value, Kind.Modifier)
+ }
+ }
+ }
+
+ fun DocumentationNode.isDeprecation() = name == "Deprecated" || name == "deprecated"
+
+ fun DocumentationNode.appendSourceLink(sourceElement: SourceElement) {
+ appendSourceLink(sourceElement.getPsi(), options.sourceLinks)
+ }
+
+ fun DocumentationNode.appendChild(descriptor: DeclarationDescriptor, kind: DocumentationReference.Kind): DocumentationNode? {
+ // do not include generated code
+ if (descriptor is CallableMemberDescriptor && descriptor.kind != CallableMemberDescriptor.Kind.DECLARATION)
+ return null
+
+ if (descriptor.isDocumented()) {
+ val node = descriptor.build()
+ append(node, kind)
+ return node
+ }
+ return null
+ }
+
+ fun DeclarationDescriptor.isDocumented(): Boolean {
+ return (options.includeNonPublic
+ || this !is MemberDescriptor
+ || this.visibility in visibleToDocumentation) &&
+ !isDocumentationSuppressed() &&
+ (!options.skipDeprecated || !isDeprecated())
+ }
+
+ fun DocumentationNode.appendMembers(descriptors: Iterable<DeclarationDescriptor>): List<DocumentationNode> {
+ val nodes = descriptors.map { descriptor ->
+ if (descriptor is CallableMemberDescriptor && descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) {
+ val baseDescriptor = descriptor.overriddenDescriptors.firstOrNull()
+ if (baseDescriptor != null) {
+ link(this, baseDescriptor, DocumentationReference.Kind.InheritedMember)
+ }
+ null
+ }
+ else {
+ val descriptorToUse = if (descriptor is ConstructorDescriptor) descriptor else descriptor.original
+ appendChild(descriptorToUse, DocumentationReference.Kind.Member)
+ }
+ }
+ return nodes.filterNotNull()
+ }
+
+ fun DocumentationNode.appendInPageChildren(descriptors: Iterable<DeclarationDescriptor>, kind: DocumentationReference.Kind) {
+ descriptors.forEach { descriptor ->
+ val node = appendChild(descriptor, kind)
+ node?.addReferenceTo(this, DocumentationReference.Kind.TopLevelPage)
+ }
+ }
+
+ fun DocumentationModule.appendFragments(fragments: Collection<PackageFragmentDescriptor>,
+ packageContent: Map<String, Content>,
+ packageDocumentationBuilder: PackageDocumentationBuilder) {
+ val allFqNames = fragments.map { it.fqName }.distinct()
+
+ for (packageName in allFqNames) {
+ val declarations = fragments.filter { it.fqName == packageName }.flatMap { it.getMemberScope().getContributedDescriptors() }
+
+ if (options.skipEmptyPackages && declarations.none { it.isDocumented() }) continue
+ logger.info(" package $packageName: ${declarations.count()} declarations")
+ val packageNode = findOrCreatePackageNode(packageName.asString(), packageContent)
+ packageDocumentationBuilder.buildPackageDocumentation(this@DocumentationBuilder, packageName, packageNode, declarations)
+ }
+ }
+
+ fun DeclarationDescriptor.build(): DocumentationNode = when (this) {
+ is ClassDescriptor -> build()
+ is ConstructorDescriptor -> build()
+ is PropertyDescriptor -> build()
+ is FunctionDescriptor -> build()
+ is TypeParameterDescriptor -> build()
+ is ValueParameterDescriptor -> build()
+ is ReceiverParameterDescriptor -> build()
+ else -> throw IllegalStateException("Descriptor $this is not known")
+ }
+
+ fun ClassDescriptor.build(): DocumentationNode {
+ val kind = when (kind) {
+ ClassKind.OBJECT -> Kind.Object
+ ClassKind.INTERFACE -> Kind.Interface
+ ClassKind.ENUM_CLASS -> Kind.Enum
+ ClassKind.ANNOTATION_CLASS -> Kind.AnnotationClass
+ ClassKind.ENUM_ENTRY -> Kind.EnumItem
+ else -> Kind.Class
+ }
+ val node = nodeForDescriptor(this, kind)
+ node.appendSupertypes(this)
+ if (getKind() != ClassKind.OBJECT && getKind() != ClassKind.ENUM_ENTRY) {
+ node.appendInPageChildren(typeConstructor.parameters, DocumentationReference.Kind.Detail)
+ val constructorsToDocument = if (getKind() == ClassKind.ENUM_CLASS)
+ constructors.filter { it.valueParameters.size > 0 }
+ else
+ constructors
+ node.appendMembers(constructorsToDocument)
+ }
+ val members = defaultType.memberScope.getContributedDescriptors().filter { it != companionObjectDescriptor }
+ node.appendMembers(members)
+ node.appendMembers(staticScope.getContributedDescriptors()).forEach {
+ it.appendTextNode("static", Kind.Modifier)
+ }
+ val companionObjectDescriptor = companionObjectDescriptor
+ if (companionObjectDescriptor != null) {
+ node.appendMembers(companionObjectDescriptor.defaultType.memberScope.getContributedDescriptors())
+ }
+ node.appendAnnotations(this)
+ node.appendModifiers(this)
+ node.appendSourceLink(source)
+ register(this, node)
+ return node
+ }
+
+ fun ConstructorDescriptor.build(): DocumentationNode {
+ val node = nodeForDescriptor(this, Kind.Constructor)
+ node.appendInPageChildren(valueParameters, DocumentationReference.Kind.Detail)
+ register(this, node)
+ return node
+ }
+
+ private fun CallableMemberDescriptor.inCompanionObject(): Boolean {
+ val containingDeclaration = containingDeclaration
+ if ((containingDeclaration as? ClassDescriptor)?.isCompanionObject ?: false) {
+ return true
+ }
+ val receiver = extensionReceiverParameter
+ return (receiver?.type?.constructor?.declarationDescriptor as? ClassDescriptor)?.isCompanionObject ?: false
+ }
+
+ fun FunctionDescriptor.build(): DocumentationNode {
+ if (ErrorUtils.containsErrorType(this)) {
+ logger.warn("Found an unresolved type in ${signatureWithSourceLocation()}")
+ }
+
+ val node = nodeForDescriptor(this, if (inCompanionObject()) Kind.CompanionObjectFunction else Kind.Function)
+
+ node.appendInPageChildren(typeParameters, DocumentationReference.Kind.Detail)
+ extensionReceiverParameter?.let { node.appendChild(it, DocumentationReference.Kind.Detail) }
+ node.appendInPageChildren(valueParameters, DocumentationReference.Kind.Detail)
+ node.appendType(returnType)
+ node.appendAnnotations(this)
+ node.appendModifiers(this)
+ node.appendSourceLink(source)
+
+ overriddenDescriptors.forEach {
+ addOverrideLink(it, this)
+ }
+
+ register(this, node)
+ return node
+ }
+
+ fun addOverrideLink(baseClassFunction: CallableMemberDescriptor, overridingFunction: CallableMemberDescriptor) {
+ val source = baseClassFunction.original.source.getPsi()
+ if (source != null) {
+ link(overridingFunction, baseClassFunction, DocumentationReference.Kind.Override)
+ } else {
+ baseClassFunction.overriddenDescriptors.forEach {
+ addOverrideLink(it, overridingFunction)
+ }
+ }
+ }
+
+ fun PropertyDescriptor.build(): DocumentationNode {
+ val node = nodeForDescriptor(this, if (inCompanionObject()) Kind.CompanionObjectProperty else Kind.Property)
+ node.appendInPageChildren(typeParameters, DocumentationReference.Kind.Detail)
+ extensionReceiverParameter?.let { node.appendChild(it, DocumentationReference.Kind.Detail) }
+ node.appendType(returnType)
+ node.appendAnnotations(this)
+ node.appendModifiers(this)
+ node.appendSourceLink(source)
+ if (isVar) {
+ node.appendTextNode("var", DocumentationNode.Kind.Modifier)
+ }
+ getter?.let {
+ if (!it.isDefault) {
+ node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Getter")
+ }
+ }
+ setter?.let {
+ if (!it.isDefault) {
+ node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Setter")
+ }
+ }
+
+ overriddenDescriptors.forEach {
+ addOverrideLink(it, this)
+ }
+
+ register(this, node)
+ return node
+ }
+
+ fun DocumentationNode.addAccessorDocumentation(documentation: Content, prefix: String) {
+ if (documentation == Content.Empty) return
+ updateContent {
+ if (!documentation.children.isEmpty()) {
+ val section = addSection(prefix, null)
+ documentation.children.forEach { section.append(it) }
+ }
+ documentation.sections.forEach {
+ val section = addSection("$prefix ${it.tag}", it.subjectName)
+ it.children.forEach { section.append(it) }
+ }
+ }
+ }
+
+ fun ValueParameterDescriptor.build(): DocumentationNode {
+ val node = nodeForDescriptor(this, Kind.Parameter)
+ node.appendType(varargElementType ?: type)
+ if (declaresDefaultValue()) {
+ val psi = source.getPsi() as? KtParameter
+ if (psi != null) {
+ val defaultValueText = psi.defaultValue?.text
+ if (defaultValueText != null) {
+ node.appendTextNode(defaultValueText, Kind.Value)
+ }
+ }
+ }
+ node.appendAnnotations(this)
+ node.appendModifiers(this)
+ if (varargElementType != null && node.details(Kind.Modifier).none { it.name == "vararg" }) {
+ node.appendTextNode("vararg", Kind.Modifier)
+ }
+ register(this, node)
+ return node
+ }
+
+ fun TypeParameterDescriptor.build(): DocumentationNode {
+ val doc = descriptorDocumentationParser.parseDocumentation(this)
+ val name = name.asString()
+ val prefix = variance.label
+
+ val node = DocumentationNode(name, doc, DocumentationNode.Kind.TypeParameter)
+ if (prefix != "") {
+ node.appendTextNode(prefix, Kind.Modifier)
+ }
+ if (isReified) {
+ node.appendTextNode("reified", Kind.Modifier)
+ }
+
+ for (constraint in upperBounds) {
+ if (KotlinBuiltIns.isDefaultBound(constraint)) {
+ continue
+ }
+ node.appendType(constraint, Kind.UpperBound)
+ }
+
+ for (constraint in lowerBounds) {
+ if (KotlinBuiltIns.isNothing(constraint))
+ continue
+ node.appendType(constraint, Kind.LowerBound)
+ }
+ return node
+ }
+
+ fun ReceiverParameterDescriptor.build(): DocumentationNode {
+ var receiverClass: DeclarationDescriptor = type.constructor.declarationDescriptor!!
+ if ((receiverClass as? ClassDescriptor)?.isCompanionObject ?: false) {
+ receiverClass = receiverClass.containingDeclaration!!
+ }
+ link(receiverClass,
+ containingDeclaration,
+ DocumentationReference.Kind.Extension)
+
+ val node = DocumentationNode(name.asString(), Content.Empty, Kind.Receiver)
+ node.appendType(type)
+ return node
+ }
+
+ fun AnnotationDescriptor.build(): DocumentationNode? {
+ val annotationClass = type.constructor.declarationDescriptor
+ if (annotationClass == null || ErrorUtils.isError(annotationClass)) {
+ return null
+ }
+ val node = DocumentationNode(annotationClass.name.asString(), Content.Empty, DocumentationNode.Kind.Annotation)
+ val arguments = allValueArguments.toList().sortedBy { it.first.index }
+ arguments.forEach {
+ val valueNode = it.second.toDocumentationNode()
+ if (valueNode != null) {
+ val paramNode = DocumentationNode(it.first.name.asString(), Content.Empty, DocumentationNode.Kind.Parameter)
+ paramNode.append(valueNode, DocumentationReference.Kind.Detail)
+ node.append(paramNode, DocumentationReference.Kind.Detail)
+ }
+ }
+ return node
+ }
+
+ fun CompileTimeConstant<Any?>.build(): DocumentationNode? = when (this) {
+ is TypedCompileTimeConstant -> constantValue.toDocumentationNode()
+ else -> null
+ }
+
+ fun ConstantValue<*>.toDocumentationNode(): DocumentationNode? = value?.let { value ->
+ when (value) {
+ is String ->
+ "\"" + StringUtil.escapeStringCharacters(value) + "\""
+ is EnumEntrySyntheticClassDescriptor ->
+ value.containingDeclaration.name.asString() + "." + value.name.asString()
+ else -> value.toString()
+ }.let { valueString ->
+ DocumentationNode(valueString, Content.Empty, DocumentationNode.Kind.Value)
+ }
+ }
+}
+
+class KotlinPackageDocumentationBuilder : PackageDocumentationBuilder {
+ override fun buildPackageDocumentation(documentationBuilder: DocumentationBuilder,
+ packageName: FqName,
+ packageNode: DocumentationNode,
+ declarations: List<DeclarationDescriptor>) {
+ val externalClassNodes = hashMapOf<FqName, DocumentationNode>()
+ declarations.forEach { descriptor ->
+ with(documentationBuilder) {
+ if (descriptor.isDocumented()) {
+ val parent = packageNode.getParentForPackageMember(descriptor, externalClassNodes)
+ parent.appendChild(descriptor, DocumentationReference.Kind.Member)
+ }
+ }
+ }
+ }
+}
+
+class KotlinJavaDocumentationBuilder
+ @Inject constructor(val documentationBuilder: DocumentationBuilder,
+ val logger: DokkaLogger) : JavaDocumentationBuilder
+{
+ override fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>) {
+ val packageNode = module.findOrCreatePackageNode(file.packageName, packageContent)
+
+ file.classes.forEach {
+ val javaDescriptorResolver = KotlinCacheService.getInstance(file.project).getProjectService(JvmPlatform,
+ it.getModuleInfo(), JavaDescriptorResolver::class.java)
+
+ val descriptor = javaDescriptorResolver.resolveClass(JavaClassImpl(it))
+ if (descriptor == null) {
+ logger.warn("Cannot find descriptor for Java class ${it.qualifiedName}")
+ }
+ else {
+ with(documentationBuilder) {
+ packageNode.appendChild(descriptor, DocumentationReference.Kind.Member)
+ }
+ }
+ }
+ }
+}
+
+private fun AnnotationDescriptor.isDocumented(): Boolean {
+ if (source.getPsi() != null && mustBeDocumented()) return true
+ val annotationClassName = type.constructor.declarationDescriptor?.fqNameSafe?.asString()
+ return annotationClassName == "kotlin.Extension"
+}
+
+fun AnnotationDescriptor.mustBeDocumented(): Boolean {
+ val annotationClass = type.constructor.declarationDescriptor as? Annotated ?: return false
+ return annotationClass.isDocumentedAnnotation()
+}
+
+fun DeclarationDescriptor.isDocumentationSuppressed(): Boolean {
+ val doc = KDocFinder.findKDoc(this)
+ return doc is KDocSection && doc.findTagByName("suppress") != null
+}
+
+fun DeclarationDescriptor.isDeprecated(): Boolean = annotations.any {
+ DescriptorUtils.getFqName(it.type.constructor.declarationDescriptor!!).asString() == "kotlin.Deprecated"
+} || (this is ConstructorDescriptor && containingDeclaration.isDeprecated())
+
+fun DocumentationNode.getParentForPackageMember(descriptor: DeclarationDescriptor,
+ externalClassNodes: MutableMap<FqName, DocumentationNode>): DocumentationNode {
+ if (descriptor is CallableMemberDescriptor) {
+ val extensionClassDescriptor = descriptor.getExtensionClassDescriptor()
+ if (extensionClassDescriptor != null && !isSamePackage(descriptor, extensionClassDescriptor) &&
+ !ErrorUtils.isError(extensionClassDescriptor)) {
+ val fqName = DescriptorUtils.getFqNameSafe(extensionClassDescriptor)
+ return externalClassNodes.getOrPut(fqName, {
+ val newNode = DocumentationNode(fqName.asString(), Content.Empty, Kind.ExternalClass)
+ append(newNode, DocumentationReference.Kind.Member)
+ newNode
+ })
+ }
+ }
+ return this
+}
+
+fun CallableMemberDescriptor.getExtensionClassDescriptor(): ClassifierDescriptor? {
+ val extensionReceiver = extensionReceiverParameter
+ if (extensionReceiver != null) {
+ val type = extensionReceiver.type
+ return type.constructor.declarationDescriptor as? ClassDescriptor
+ }
+ return null
+}
+
+fun DeclarationDescriptor.signature(): String = when(this) {
+ is ClassDescriptor, is PackageFragmentDescriptor -> DescriptorUtils.getFqName(this).asString()
+ is PropertyDescriptor -> containingDeclaration.signature() + "#" + name + receiverSignature()
+ is FunctionDescriptor -> containingDeclaration.signature() + "#" + name + parameterSignature()
+ is ValueParameterDescriptor -> containingDeclaration.signature() + ":" + name
+ is TypeParameterDescriptor -> containingDeclaration.signature() + "<" + name
+
+ else -> throw UnsupportedOperationException("Don't know how to calculate signature for $this")
+}
+
+fun PropertyDescriptor.receiverSignature(): String {
+ val receiver = extensionReceiverParameter
+ if (receiver != null) {
+ return "#" + receiver.type.signature()
+ }
+ return ""
+}
+
+fun CallableMemberDescriptor.parameterSignature(): String {
+ val params = valueParameters.map { it.type }.toArrayList()
+ val extensionReceiver = extensionReceiverParameter
+ if (extensionReceiver != null) {
+ params.add(0, extensionReceiver.type)
+ }
+ return "(" + params.map { it.signature() }.joinToString() + ")"
+}
+
+fun KotlinType.signature(): String {
+ val declarationDescriptor = constructor.declarationDescriptor ?: return "<null>"
+ val typeName = DescriptorUtils.getFqName(declarationDescriptor).asString()
+ if (typeName == "Array" && arguments.size == 1) {
+ return "Array<" + arguments.first().type.signature() + ">"
+ }
+ return typeName
+}
+
+fun DeclarationDescriptor.signatureWithSourceLocation(): String {
+ val signature = signature()
+ val sourceLocation = sourceLocation()
+ return if (sourceLocation != null) "$signature ($sourceLocation)" else signature
+}
+
+fun DeclarationDescriptor.sourceLocation(): String? {
+ if (this is DeclarationDescriptorWithSource) {
+ val psi = (this.source as? PsiSourceElement)?.getPsi()
+ if (psi != null) {
+ val fileName = psi.containingFile.name
+ val lineNumber = psi.lineNumber()
+ return if (lineNumber != null) "$fileName:$lineNumber" else fileName
+ }
+ }
+ return null
+}
diff --git a/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt
new file mode 100644
index 00000000..7a1d591c
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt
@@ -0,0 +1,64 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiNamedElement
+import org.jetbrains.dokka.Kotlin.DescriptorDocumentationParser
+import org.jetbrains.kotlin.asJava.KtLightElement
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.KtParameter
+import org.jetbrains.kotlin.psi.KtPropertyAccessor
+
+class KotlinAsJavaDocumentationBuilder
+ @Inject constructor(val kotlinAsJavaDocumentationParser: KotlinAsJavaDocumentationParser) : PackageDocumentationBuilder
+{
+ override fun buildPackageDocumentation(documentationBuilder: DocumentationBuilder,
+ packageName: FqName,
+ packageNode: DocumentationNode,
+ declarations: List<DeclarationDescriptor>) {
+ val project = documentationBuilder.resolutionFacade.project
+ val psiPackage = JavaPsiFacade.getInstance(project).findPackage(packageName.asString())
+ if (psiPackage == null) {
+ documentationBuilder.logger.error("Cannot find Java package by qualified name: ${packageName.asString()}")
+ return
+ }
+
+ val javaDocumentationBuilder = JavaPsiDocumentationBuilder(documentationBuilder.options,
+ documentationBuilder.refGraph,
+ kotlinAsJavaDocumentationParser)
+
+ psiPackage.classes.filter { it is KtLightElement<*, *> }.filter { it.isVisibleInDocumentation() }.forEach {
+ javaDocumentationBuilder.appendClasses(packageNode, arrayOf(it))
+ }
+ }
+
+ fun PsiClass.isVisibleInDocumentation() : Boolean {
+ val origin: KtDeclaration? = (this as KtLightElement<*, *>).getOrigin()
+ return origin?.hasModifier(KtTokens.INTERNAL_KEYWORD) != true &&
+ origin?.hasModifier(KtTokens.PRIVATE_KEYWORD) != true
+ }
+}
+
+class KotlinAsJavaDocumentationParser
+ @Inject constructor(val resolutionFacade: DokkaResolutionFacade,
+ val descriptorDocumentationParser: DescriptorDocumentationParser) : JavaDocumentationParser
+{
+ override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult {
+ val kotlinLightElement = element as? KtLightElement<*, *> ?: return JavadocParseResult.Empty
+ val origin = kotlinLightElement.getOrigin() ?: return JavadocParseResult.Empty
+ if (origin is KtParameter) {
+ // LazyDeclarationResolver does not support setter parameters
+ val grandFather = origin.parent?.parent
+ if (grandFather is KtPropertyAccessor) {
+ return JavadocParseResult.Empty
+ }
+ }
+ val descriptor = resolutionFacade.resolveToDescriptor(origin)
+ val content = descriptorDocumentationParser.parseDocumentation(descriptor, origin is KtParameter)
+ return JavadocParseResult(content, null)
+ }
+}
diff --git a/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt
new file mode 100644
index 00000000..0d39f410
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt
@@ -0,0 +1,409 @@
+package org.jetbrains.dokka
+
+import org.jetbrains.dokka.LanguageService.RenderMode
+
+/**
+ * Implements [LanguageService] and provides rendering of symbols in Kotlin language
+ */
+class KotlinLanguageService : LanguageService {
+ private val fullOnlyModifiers = setOf("public", "protected", "private", "inline", "noinline", "crossinline", "reified")
+
+ override fun render(node: DocumentationNode, renderMode: RenderMode): ContentNode {
+ return content {
+ when (node.kind) {
+ DocumentationNode.Kind.Package -> if (renderMode == RenderMode.FULL) renderPackage(node)
+ in DocumentationNode.Kind.classLike -> renderClass(node, renderMode)
+
+ DocumentationNode.Kind.EnumItem,
+ DocumentationNode.Kind.ExternalClass -> if (renderMode == RenderMode.FULL) identifier(node.name)
+
+ DocumentationNode.Kind.TypeParameter -> renderTypeParameter(node, renderMode)
+ DocumentationNode.Kind.Type,
+ DocumentationNode.Kind.UpperBound -> renderType(node, renderMode)
+
+ DocumentationNode.Kind.Modifier -> renderModifier(node)
+ DocumentationNode.Kind.Constructor,
+ DocumentationNode.Kind.Function,
+ DocumentationNode.Kind.CompanionObjectFunction -> renderFunction(node, renderMode)
+ DocumentationNode.Kind.Property,
+ DocumentationNode.Kind.CompanionObjectProperty -> renderProperty(node, renderMode)
+ else -> identifier(node.name)
+ }
+ }
+ }
+
+ override fun renderName(node: DocumentationNode): String {
+ return when (node.kind) {
+ DocumentationNode.Kind.Constructor -> node.owner!!.name
+ else -> node.name
+ }
+ }
+
+ override fun summarizeSignatures(nodes: List<DocumentationNode>): ContentNode? {
+ if (nodes.size < 2) return null
+ val receiverKind = nodes.getReceiverKind() ?: return null
+ val functionWithTypeParameter = nodes.firstOrNull { it.details(DocumentationNode.Kind.TypeParameter).any() } ?: return null
+ return content {
+ val typeParameter = functionWithTypeParameter.details(DocumentationNode.Kind.TypeParameter).first()
+ if (functionWithTypeParameter.kind == DocumentationNode.Kind.Function) {
+ renderFunction(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name))
+ }
+ else {
+ renderProperty(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name))
+ }
+ }
+ }
+
+ private fun List<DocumentationNode>.getReceiverKind(): ReceiverKind? {
+ val qNames = map { it.getReceiverQName() }.filterNotNull()
+ if (qNames.size != size)
+ return null
+
+ return ReceiverKind.values.firstOrNull { kind -> qNames.all { it in kind.classes } }
+ }
+
+ private fun DocumentationNode.getReceiverQName(): String? {
+ if (kind != DocumentationNode.Kind.Function && kind != DocumentationNode.Kind.Property) return null
+ val receiver = details(DocumentationNode.Kind.Receiver).singleOrNull() ?: return null
+ return receiver.detail(DocumentationNode.Kind.Type).qualifiedNameFromType()
+ }
+
+ companion object {
+ private val arrayClasses = setOf(
+ "kotlin.Array",
+ "kotlin.BooleanArray",
+ "kotlin.ByteArray",
+ "kotlin.CharArray",
+ "kotlin.ShortArray",
+ "kotlin.IntArray",
+ "kotlin.LongArray",
+ "kotlin.FloatArray",
+ "kotlin.DoubleArray"
+ )
+
+ private val arrayOrListClasses = setOf("kotlin.List") + arrayClasses
+
+ private val iterableClasses = setOf(
+ "kotlin.Collection",
+ "kotlin.Sequence",
+ "kotlin.Iterable",
+ "kotlin.Map",
+ "kotlin.String",
+ "kotlin.CharSequence") + arrayOrListClasses
+ }
+
+ private enum class ReceiverKind(val receiverName: String, val classes: Collection<String>) {
+ ARRAY("any_array", arrayClasses),
+ ARRAY_OR_LIST("any_array_or_list", arrayOrListClasses),
+ ITERABLE("any_iterable", iterableClasses),
+ }
+
+ interface SignatureMapper {
+ fun renderReceiver(receiver: DocumentationNode, to: ContentBlock)
+ }
+
+ private class SummarizingMapper(val kind: ReceiverKind, val typeParameterName: String): SignatureMapper {
+ override fun renderReceiver(receiver: DocumentationNode, to: ContentBlock) {
+ to.append(ContentIdentifier(kind.receiverName, IdentifierKind.SummarizedTypeName))
+ to.text("<$typeParameterName>")
+ }
+ }
+
+ private fun ContentBlock.renderPackage(node: DocumentationNode) {
+ keyword("package")
+ text(" ")
+ identifier(node.name)
+ }
+
+ private fun ContentBlock.renderList(nodes: List<DocumentationNode>, separator: String = ", ",
+ noWrap: Boolean = false, renderItem: (DocumentationNode) -> Unit) {
+ if (nodes.none())
+ return
+ renderItem(nodes.first())
+ nodes.drop(1).forEach {
+ if (noWrap) {
+ symbol(separator.removeSuffix(" "))
+ nbsp()
+ } else {
+ symbol(separator)
+ }
+ renderItem(it)
+ }
+ }
+
+ private fun ContentBlock.renderLinked(node: DocumentationNode, body: ContentBlock.(DocumentationNode)->Unit) {
+ val to = node.links.firstOrNull()
+ if (to == null)
+ body(node)
+ else
+ link(to) {
+ body(node)
+ }
+ }
+
+ private fun ContentBlock.renderType(node: DocumentationNode, renderMode: RenderMode) {
+ var typeArguments = node.details(DocumentationNode.Kind.Type)
+ if (node.name == "Function${typeArguments.count() - 1}") {
+ // lambda
+ val isExtension = node.annotations.any { it.name == "Extension" }
+ if (isExtension) {
+ renderType(typeArguments.first(), renderMode)
+ symbol(".")
+ typeArguments = typeArguments.drop(1)
+ }
+ symbol("(")
+ renderList(typeArguments.take(typeArguments.size - 1), noWrap = true) {
+ renderType(it, renderMode)
+ }
+ symbol(")")
+ nbsp()
+ symbol("->")
+ nbsp()
+ renderType(typeArguments.last(), renderMode)
+ return
+ }
+ if (renderMode == RenderMode.FULL) {
+ renderAnnotationsForNode(node)
+ }
+ renderModifiersForNode(node, renderMode, true)
+ renderLinked(node) { identifier(it.name, IdentifierKind.TypeName) }
+ if (typeArguments.any()) {
+ symbol("<")
+ renderList(typeArguments, noWrap = true) {
+ renderType(it, renderMode)
+ }
+ symbol(">")
+ }
+ val nullabilityModifier = node.details(DocumentationNode.Kind.NullabilityModifier).singleOrNull()
+ if (nullabilityModifier != null) {
+ symbol(nullabilityModifier.name)
+ }
+ }
+
+ private fun ContentBlock.renderModifier(node: DocumentationNode, nowrap: Boolean = false) {
+ when (node.name) {
+ "final", "public", "var" -> {}
+ else -> {
+ keyword(node.name)
+ if (nowrap) {
+ nbsp()
+ }
+ else {
+ text(" ")
+ }
+ }
+ }
+ }
+
+ private fun ContentBlock.renderTypeParameter(node: DocumentationNode, renderMode: RenderMode) {
+ renderModifiersForNode(node, renderMode, true)
+
+ identifier(node.name)
+
+ val constraints = node.details(DocumentationNode.Kind.UpperBound)
+ if (constraints.any()) {
+ nbsp()
+ symbol(":")
+ nbsp()
+ renderList(constraints, noWrap=true) {
+ renderType(it, renderMode)
+ }
+ }
+ }
+ private fun ContentBlock.renderParameter(node: DocumentationNode, renderMode: RenderMode) {
+ if (renderMode == RenderMode.FULL) {
+ renderAnnotationsForNode(node)
+ }
+ renderModifiersForNode(node, renderMode)
+ identifier(node.name, IdentifierKind.ParameterName)
+ symbol(":")
+ nbsp()
+ val parameterType = node.detail(DocumentationNode.Kind.Type)
+ renderType(parameterType, renderMode)
+ val valueNode = node.details(DocumentationNode.Kind.Value).firstOrNull()
+ if (valueNode != null) {
+ nbsp()
+ symbol("=")
+ nbsp()
+ text(valueNode.name)
+ }
+ }
+
+ private fun ContentBlock.renderTypeParametersForNode(node: DocumentationNode, renderMode: RenderMode) {
+ val typeParameters = node.details(DocumentationNode.Kind.TypeParameter)
+ if (typeParameters.any()) {
+ symbol("<")
+ renderList(typeParameters) {
+ renderTypeParameter(it, renderMode)
+ }
+ symbol(">")
+ }
+ }
+
+ private fun ContentBlock.renderSupertypesForNode(node: DocumentationNode, renderMode: RenderMode) {
+ val supertypes = node.details(DocumentationNode.Kind.Supertype)
+ if (supertypes.any()) {
+ nbsp()
+ symbol(":")
+ nbsp()
+ renderList(supertypes) {
+ indentedSoftLineBreak()
+ renderType(it, renderMode)
+ }
+ }
+ }
+
+ private fun ContentBlock.renderModifiersForNode(node: DocumentationNode,
+ renderMode: RenderMode,
+ nowrap: Boolean = false) {
+ val modifiers = node.details(DocumentationNode.Kind.Modifier)
+ for (it in modifiers) {
+ if (node.kind == org.jetbrains.dokka.DocumentationNode.Kind.Interface && it.name == "abstract")
+ continue
+ if (renderMode == RenderMode.SUMMARY && it.name in fullOnlyModifiers) {
+ continue
+ }
+ renderModifier(it, nowrap)
+ }
+ }
+
+ private fun ContentBlock.renderAnnotationsForNode(node: DocumentationNode) {
+ node.annotations.forEach {
+ renderAnnotation(it)
+ }
+ }
+
+ private fun ContentBlock.renderAnnotation(node: DocumentationNode) {
+ identifier("@" + node.name, IdentifierKind.AnnotationName)
+ val parameters = node.details(DocumentationNode.Kind.Parameter)
+ if (!parameters.isEmpty()) {
+ symbol("(")
+ renderList(parameters) {
+ text(it.detail(DocumentationNode.Kind.Value).name)
+ }
+ symbol(")")
+ }
+ text(" ")
+ }
+
+ private fun ContentBlock.renderClass(node: DocumentationNode, renderMode: RenderMode) {
+ if (renderMode == RenderMode.FULL) {
+ renderAnnotationsForNode(node)
+ }
+ renderModifiersForNode(node, renderMode)
+ when (node.kind) {
+ DocumentationNode.Kind.Class,
+ DocumentationNode.Kind.AnnotationClass,
+ DocumentationNode.Kind.Enum -> keyword("class ")
+ DocumentationNode.Kind.Interface -> keyword("interface ")
+ DocumentationNode.Kind.EnumItem -> keyword("enum val ")
+ DocumentationNode.Kind.Object -> keyword("object ")
+ else -> throw IllegalArgumentException("Node $node is not a class-like object")
+ }
+
+ identifierOrDeprecated(node)
+ renderTypeParametersForNode(node, renderMode)
+ renderSupertypesForNode(node, renderMode)
+ }
+
+ private fun ContentBlock.renderFunction(node: DocumentationNode,
+ renderMode: RenderMode,
+ signatureMapper: SignatureMapper? = null) {
+ if (renderMode == RenderMode.FULL) {
+ renderAnnotationsForNode(node)
+ }
+ renderModifiersForNode(node, renderMode)
+ when (node.kind) {
+ DocumentationNode.Kind.Constructor -> identifier(node.owner!!.name)
+ DocumentationNode.Kind.Function,
+ DocumentationNode.Kind.CompanionObjectFunction -> keyword("fun ")
+ else -> throw IllegalArgumentException("Node $node is not a function-like object")
+ }
+ renderTypeParametersForNode(node, renderMode)
+ if (node.details(DocumentationNode.Kind.TypeParameter).any()) {
+ text(" ")
+ }
+
+ renderReceiver(node, renderMode, signatureMapper)
+
+ if (node.kind != org.jetbrains.dokka.DocumentationNode.Kind.Constructor)
+ identifierOrDeprecated(node)
+
+ symbol("(")
+ val parameters = node.details(DocumentationNode.Kind.Parameter)
+ renderList(parameters) {
+ indentedSoftLineBreak()
+ renderParameter(it, renderMode)
+ }
+ if (needReturnType(node)) {
+ if (parameters.isNotEmpty()) {
+ softLineBreak()
+ }
+ symbol(")")
+ symbol(": ")
+ renderType(node.detail(DocumentationNode.Kind.Type), renderMode)
+ }
+ else {
+ symbol(")")
+ }
+ }
+
+ private fun ContentBlock.renderReceiver(node: DocumentationNode, renderMode: RenderMode, signatureMapper: SignatureMapper?) {
+ val receiver = node.details(DocumentationNode.Kind.Receiver).singleOrNull()
+ if (receiver != null) {
+ if (signatureMapper != null) {
+ signatureMapper.renderReceiver(receiver, this)
+ } else {
+ renderType(receiver.detail(DocumentationNode.Kind.Type), renderMode)
+ }
+ symbol(".")
+ }
+ }
+
+ private fun needReturnType(node: DocumentationNode) = when(node.kind) {
+ DocumentationNode.Kind.Constructor -> false
+ else -> !node.isUnitReturnType()
+ }
+
+ fun DocumentationNode.isUnitReturnType(): Boolean =
+ detail(DocumentationNode.Kind.Type).hiddenLinks.firstOrNull()?.qualifiedName() == "kotlin.Unit"
+
+ private fun ContentBlock.renderProperty(node: DocumentationNode,
+ renderMode: RenderMode,
+ signatureMapper: SignatureMapper? = null) {
+ if (renderMode == RenderMode.FULL) {
+ renderAnnotationsForNode(node)
+ }
+ renderModifiersForNode(node, renderMode)
+ when (node.kind) {
+ DocumentationNode.Kind.Property,
+ DocumentationNode.Kind.CompanionObjectProperty -> keyword("${node.getPropertyKeyword()} ")
+ else -> throw IllegalArgumentException("Node $node is not a property")
+ }
+ renderTypeParametersForNode(node, renderMode)
+ if (node.details(DocumentationNode.Kind.TypeParameter).any()) {
+ text(" ")
+ }
+
+ renderReceiver(node, renderMode, signatureMapper)
+
+ identifierOrDeprecated(node)
+ symbol(": ")
+ renderType(node.detail(DocumentationNode.Kind.Type), renderMode)
+ }
+
+ fun DocumentationNode.getPropertyKeyword() =
+ if (details(DocumentationNode.Kind.Modifier).any { it.name == "var" }) "var" else "val"
+
+ fun ContentBlock.identifierOrDeprecated(node: DocumentationNode) {
+ if (node.deprecation != null) {
+ val strike = ContentStrikethrough()
+ strike.identifier(node.name)
+ append(strike)
+ } else {
+ identifier(node.name)
+ }
+ }
+}
+
+fun DocumentationNode.qualifiedNameFromType() = (links.firstOrNull() ?: hiddenLinks.firstOrNull())?.qualifiedName() ?: name