aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/Java
diff options
context:
space:
mode:
authorPaweł Marks <pmarks@virtuslab.com>2019-10-31 15:26:20 +0100
committerPaweł Marks <pmarks@virtuslab.com>2019-10-31 15:26:20 +0100
commit50111daf07c7afd1c1b60e9672ed6786c96efdea (patch)
tree0b4420c2dc74b48c6cea577421d3fbfd3b18d95b /core/src/main/kotlin/Java
parent7c30624e3d0868346823b15b5cea5e29a56357f2 (diff)
downloaddokka-50111daf07c7afd1c1b60e9672ed6786c96efdea.tar.gz
dokka-50111daf07c7afd1c1b60e9672ed6786c96efdea.tar.bz2
dokka-50111daf07c7afd1c1b60e9672ed6786c96efdea.zip
Make things compile, no matter the cost
Diffstat (limited to 'core/src/main/kotlin/Java')
-rw-r--r--core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt344
-rw-r--r--core/src/main/kotlin/Java/JavadocParser.kt402
2 files changed, 0 insertions, 746 deletions
diff --git a/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt b/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt
deleted file mode 100644
index d3fc7048..00000000
--- a/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt
+++ /dev/null
@@ -1,344 +0,0 @@
-package org.jetbrains.dokka
-
-import com.google.inject.Inject
-import com.intellij.openapi.util.text.StringUtil
-import com.intellij.psi.*
-import com.intellij.psi.impl.JavaConstantExpressionEvaluator
-import com.intellij.psi.util.InheritanceUtil
-import com.intellij.psi.util.PsiTreeUtil
-import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation
-import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration
-import org.jetbrains.kotlin.asJava.elements.KtLightElement
-import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
-import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
-import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.psi.KtModifierListOwner
-
-fun getSignature(element: PsiElement?) = when(element) {
- is PsiPackage -> element.qualifiedName
- is PsiClass -> element.qualifiedName
- is PsiField -> element.containingClass!!.qualifiedName + "$" + element.name
- is PsiMethod ->
- methodSignature(element)
- is PsiParameter -> {
- val method = (element.parent.parent as PsiMethod)
- methodSignature(method)
- }
- else -> null
-}
-
-private fun methodSignature(method: PsiMethod): String {
- return method.containingClass!!.qualifiedName + "$" + method.name + "(" +
- method.parameterList.parameters.map { it.type.typeSignature() }.joinToString(",") + ")"
-}
-
-private fun PsiType.typeSignature(): String = when(this) {
- is PsiArrayType -> "Array((${componentType.typeSignature()}))"
- is PsiPrimitiveType -> "kotlin." + canonicalText.capitalize()
- else -> mapTypeName(this)
-}
-
-private fun mapTypeName(psiType: PsiType): String = when (psiType) {
- is PsiPrimitiveType -> psiType.canonicalText
- is PsiClassType -> psiType.resolve()?.qualifiedName ?: psiType.className
- is PsiEllipsisType -> mapTypeName(psiType.componentType)
- is PsiArrayType -> "kotlin.Array"
- else -> psiType.canonicalText
-}
-
-interface JavaDocumentationBuilder {
- fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>)
-}
-
-class JavaPsiDocumentationBuilder : JavaDocumentationBuilder {
- private val passConfiguration: DokkaConfiguration.PassConfiguration
- private val refGraph: NodeReferenceGraph
- private val docParser: JavaDocumentationParser
- private val documentationBuilder: DocumentationBuilder
-
- @Inject constructor(
- documentationBuilder: DocumentationBuilder,
- refGraph: NodeReferenceGraph,
- logger: DokkaLogger,
- signatureProvider: ElementSignatureProvider,
- externalDocumentationLinkResolver: ExternalDocumentationLinkResolver
- ) {
- this.passConfiguration = documentationBuilder.passConfiguration
- this.documentationBuilder = documentationBuilder
- this.refGraph = refGraph
- this.docParser = JavadocParser(refGraph, logger, signatureProvider, externalDocumentationLinkResolver)
- }
-
- constructor(documentationBuilder: DocumentationBuilder, refGraph: NodeReferenceGraph, docParser: JavaDocumentationParser) {
- this.passConfiguration = documentationBuilder.passConfiguration
- this.refGraph = refGraph
- this.docParser = docParser
- this.documentationBuilder = documentationBuilder
- }
-
- override fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>) {
- if (skipFile(file) || file.classes.all { skipElement(it) }) {
- return
- }
- val packageNode = documentationBuilder.findOrCreatePackageNode(module, file.packageName, emptyMap(), refGraph)
- appendClasses(packageNode, file.classes)
- }
-
- fun appendClasses(packageNode: DocumentationNode, classes: Array<PsiClass>) {
- packageNode.appendChildren(classes) { build() }
- }
-
- fun register(element: PsiElement, node: DocumentationNode) {
- val signature = getSignature(element)
- if (signature != null) {
- refGraph.register(signature, node)
- }
- }
-
- fun link(node: DocumentationNode, element: PsiElement?) {
- val qualifiedName = getSignature(element)
- if (qualifiedName != null) {
- refGraph.link(node, qualifiedName, RefKind.Link)
- }
- }
-
- fun link(element: PsiElement?, node: DocumentationNode, kind: RefKind) {
- val qualifiedName = getSignature(element)
- if (qualifiedName != null) {
- refGraph.link(qualifiedName, node, kind)
- }
- }
-
- fun nodeForElement(element: PsiNamedElement,
- kind: DocumentationNodes,
- name: String = element.name ?: "<anonymous>",
- register: Boolean = false): DocumentationNode {
- val (docComment, deprecatedContent) = docParser.parseDocumentation(element)
- val node = DocumentationNode(name, docComment, kind)
- if (register) register(element, node)
- if (element is PsiModifierListOwner) {
- node.appendModifiers(element)
- val modifierList = element.modifierList
- if (modifierList != null) {
- modifierList.annotations.filter { !ignoreAnnotation(it) }.forEach {
- val annotation = it.build()
- node.append(annotation,
- if (it.qualifiedName == "java.lang.Deprecated") RefKind.Deprecation else RefKind.Annotation)
- }
- }
- }
- return node
- }
-
- fun ignoreAnnotation(annotation: PsiAnnotation) = when(annotation.qualifiedName) {
- "java.lang.SuppressWarnings" -> true
- else -> false
- }
-
- fun <T : Any> DocumentationNode.appendChildren(elements: Array<T>,
- kind: RefKind = RefKind.Member,
- buildFn: T.() -> DocumentationNode) {
- elements.forEach {
- if (!skipElement(it)) {
- append(it.buildFn(), kind)
- }
- }
- }
-
- private fun skipFile(javaFile: PsiJavaFile): Boolean = passConfiguration.effectivePackageOptions(javaFile.packageName).suppress
-
- private fun skipElement(element: Any) =
- skipElementByVisibility(element) ||
- hasSuppressDocTag(element) ||
- skipElementBySuppressedFiles(element)
-
- private fun skipElementByVisibility(element: Any): Boolean =
- element is PsiModifierListOwner &&
- element !is PsiParameter &&
- !(passConfiguration.effectivePackageOptions((element.containingFile as? PsiJavaFile)?.packageName ?: "").includeNonPublic) &&
- (element.hasModifierProperty(PsiModifier.PRIVATE) ||
- element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) ||
- element.isInternal())
-
- private fun skipElementBySuppressedFiles(element: Any): Boolean =
- element is PsiElement && element.containingFile.virtualFile.path in passConfiguration.suppressedFiles
-
- private fun PsiElement.isInternal(): Boolean {
- val ktElement = (this as? KtLightElement<*, *>)?.kotlinOrigin ?: return false
- return (ktElement as? KtModifierListOwner)?.hasModifier(KtTokens.INTERNAL_KEYWORD) ?: false
- }
-
- fun <T : Any> DocumentationNode.appendMembers(elements: Array<T>, buildFn: T.() -> DocumentationNode) =
- appendChildren(elements, RefKind.Member, buildFn)
-
- fun <T : Any> DocumentationNode.appendDetails(elements: Array<T>, buildFn: T.() -> DocumentationNode) =
- appendChildren(elements, RefKind.Detail, buildFn)
-
- fun PsiClass.build(): DocumentationNode {
- val kind = when {
- isAnnotationType -> DocumentationNodes.AnnotationClass
- isInterface -> DocumentationNodes.Interface
- isEnum -> DocumentationNodes.Enum
- isException() -> DocumentationNodes.Exception
- else -> DocumentationNodes.Class
- }
- val node = nodeForElement(this, kind, register = isAnnotationType)
- superTypes.filter { !ignoreSupertype(it) }.forEach {
- node.appendType(it, DocumentationNodes.Supertype)
- val superClass = it.resolve()
- if (superClass != null) {
- link(superClass, node, RefKind.Inheritor)
- }
- }
- node.appendDetails(typeParameters) { build() }
- node.appendMembers(methods) { build() }
- node.appendMembers(fields) { build() }
- node.appendMembers(innerClasses) { build() }
- register(this, node)
- return node
- }
-
- fun PsiClass.isException() = InheritanceUtil.isInheritor(this, "java.lang.Throwable")
-
- fun ignoreSupertype(psiType: PsiClassType): Boolean =
- psiType.isClass("java.lang.Enum") || psiType.isClass("java.lang.Object")
-
- fun PsiClassType.isClass(qName: String): Boolean {
- val shortName = qName.substringAfterLast('.')
- if (className == shortName) {
- val psiClass = resolve()
- return psiClass?.qualifiedName == qName
- }
- return false
- }
-
- fun PsiField.build(): DocumentationNode {
- val node = nodeForElement(this, nodeKind())
- node.appendType(type)
-
- node.appendConstantValueIfAny(this)
- register(this, node)
- return node
- }
-
- private fun DocumentationNode.appendConstantValueIfAny(field: PsiField) {
- val modifierList = field.modifierList ?: return
- val initializer = field.initializer ?: return
- if (modifierList.hasExplicitModifier(PsiModifier.FINAL) &&
- modifierList.hasExplicitModifier(PsiModifier.STATIC)) {
- val value = JavaConstantExpressionEvaluator.computeConstantExpression(initializer, false)
- val text = when(value) {
- null -> return // No value found
- is String ->
- "\"" + StringUtil.escapeStringCharacters(value) + "\""
- else -> value.toString()
- }
- append(DocumentationNode(text, Content.Empty, DocumentationNodes.Value), RefKind.Detail)
- }
- }
-
- private fun PsiField.nodeKind(): DocumentationNodes = when {
- this is PsiEnumConstant -> DocumentationNodes.EnumItem
- else -> DocumentationNodes.Field
- }
-
- fun PsiMethod.build(): DocumentationNode {
- val node = nodeForElement(this, nodeKind(),
- if (isConstructor) "<init>" else name)
-
- if (!isConstructor) {
- node.appendType(returnType)
- }
- node.appendDetails(parameterList.parameters) { build() }
- node.appendDetails(typeParameters) { build() }
- register(this, node)
- return node
- }
-
- private fun PsiMethod.nodeKind(): DocumentationNodes = when {
- isConstructor -> DocumentationNodes.Constructor
- else -> DocumentationNodes.Function
- }
-
- fun PsiParameter.build(): DocumentationNode {
- val node = nodeForElement(this, DocumentationNodes.Parameter::class)
- node.appendType(type)
- return node
- }
-
- fun PsiTypeParameter.build(): DocumentationNode {
- val node = nodeForElement(this, DocumentationNodes.TypeParameter)
- extendsListTypes.forEach { node.appendType(it, DocumentationNodes.UpperBound) }
- implementsListTypes.forEach { node.appendType(it, DocumentationNodes.UpperBound) }
- return node
- }
-
- fun DocumentationNode.appendModifiers(element: PsiModifierListOwner) {
- val modifierList = element.modifierList ?: return
-
- PsiModifier.MODIFIERS.forEach {
- if (modifierList.hasExplicitModifier(it)) {
- appendTextNode(it, DocumentationNodes.Modifier)
- }
- }
- }
-
- fun DocumentationNode.appendType(psiType: PsiType?, kind: DocumentationNodes = DocumentationNodes.Type) {
- if (psiType == null) {
- return
- }
- append(psiType.build(kind), RefKind.Detail)
- }
-
- fun PsiType.build(kind: DocumentationNodes = DocumentationNodes.Type): DocumentationNode {
- val name = mapTypeName(this)
- val node = DocumentationNode(name, Content.Empty, kind)
- if (this is PsiClassType) {
- node.appendDetails(parameters) { build(DocumentationNodes.Type) }
- link(node, resolve())
- }
- if (this is PsiArrayType && this !is PsiEllipsisType) {
- node.append(componentType.build(DocumentationNodes.Type), RefKind.Detail)
- }
- return node
- }
-
- private fun lookupOrBuildClass(psiClass: PsiClass): DocumentationNode {
- val existing = refGraph.lookup(getSignature(psiClass)!!)
- if (existing != null) return existing
- val new = psiClass.build()
- val packageNode = documentation. findOrCreatePackageNode(null, (psiClass.containingFile as PsiJavaFile).packageName, emptyMap(), refGraph)
- packageNode.append(new, RefKind.Member)
- return new
- }
-
- fun PsiAnnotation.build(): DocumentationNode {
-
- val original = when (this) {
- is KtLightAbstractAnnotation -> clsDelegate
- else -> this
- }
- val node = DocumentationNodes.Annotation(qualifiedName?.substringAfterLast(".") ?: "<?>")
- val psiClass = original.nameReferenceElement?.resolve() as? PsiClass
- if (psiClass != null && psiClass.isAnnotationType) {
- node.append(lookupOrBuildClass(psiClass), RefKind.Link)
- }
- parameterList.attributes.forEach {
- val parameter = DocumentationNodes.Parameter(it.name ?: "value", this.extractDescriptor())
- val value = it.value
- if (value != null) {
- val valueText = (value as? PsiLiteralExpression)?.value as? String ?: value.text
- val valueNode = DocumentationNode(valueText, Content.Empty, DocumentationNodes.Value)
- parameter.append(valueNode, RefKind.Detail)
- }
- node.append(parameter, RefKind.Detail)m
- }
- return node
- }
-}
-
-fun hasSuppressDocTag(element: Any?): Boolean {
- val declaration = (element as? KtLightDeclaration<*, *>)?.kotlinOrigin ?: return false
- return PsiTreeUtil.findChildrenOfType(declaration.docComment, KDocTag::class.java).any { it.knownTag == KDocKnownTag.SUPPRESS }
-}
-
diff --git a/core/src/main/kotlin/Java/JavadocParser.kt b/core/src/main/kotlin/Java/JavadocParser.kt
deleted file mode 100644
index 25a974a3..00000000
--- a/core/src/main/kotlin/Java/JavadocParser.kt
+++ /dev/null
@@ -1,402 +0,0 @@
-package org.jetbrains.dokka
-
-import com.intellij.psi.*
-import com.intellij.psi.impl.source.tree.JavaDocElementType
-import com.intellij.psi.javadoc.*
-import com.intellij.psi.util.PsiTreeUtil
-import com.intellij.util.containers.isNullOrEmpty
-import org.jetbrains.kotlin.utils.keysToMap
-import org.jsoup.Jsoup
-import org.jsoup.nodes.Element
-import org.jsoup.nodes.Node
-import org.jsoup.nodes.TextNode
-import java.net.URI
-
-data class JavadocParseResult(val content: Content, val deprecatedContent: Content?) {
- companion object {
- val Empty = JavadocParseResult(Content.Empty, null)
- }
-}
-
-interface JavaDocumentationParser {
- fun parseDocumentation(element: PsiNamedElement): JavadocParseResult
-}
-
-class JavadocParser(
- private val refGraph: NodeReferenceGraph,
- private val logger: DokkaLogger,
- private val signatureProvider: ElementSignatureProvider,
- private val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver
-) : JavaDocumentationParser {
-
- private fun ContentSection.appendTypeElement(signature: String, selector: (DocumentationNode) -> DocumentationNode?) {
- append(LazyContentBlock {
- val node = refGraph.lookupOrWarn(signature, logger)?.let(selector) ?: return@LazyContentBlock emptyList()
- listOf(ContentBlock().apply {
- append(NodeRenderContent(node, LanguageService.RenderMode.SUMMARY))
- symbol(":")
- text(" ")
- })
- })
- }
-
- override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult {
- val docComment = (element as? PsiDocCommentOwner)?.docComment ?: return JavadocParseResult.Empty
- val result = MutableContent()
- var deprecatedContent: Content? = null
-
- val nodes = convertJavadocElements(docComment.descriptionElements.dropWhile { it.text.trim().isEmpty() }, element)
- val firstParagraphContents = nodes.takeWhile { it !is ContentParagraph }
- val firstParagraph = ContentParagraph()
- if (firstParagraphContents.isNotEmpty()) {
- firstParagraphContents.forEach { firstParagraph.append(it) }
- result.append(firstParagraph)
- }
-
- result.appendAll(nodes.drop(firstParagraphContents.size))
-
- if (element is PsiMethod) {
- val tagsByName = element.searchInheritedTags()
- for ((tagName, tags) in tagsByName) {
- for ((tag, context) in tags) {
- val section = result.addSection(javadocSectionDisplayName(tagName), tag.getSubjectName())
- val signature = signatureProvider.signature(element)
- when (tagName) {
- "param" -> {
- section.appendTypeElement(signature) {
- it.details
- .find { node -> node.kind == NodeKind.Parameter && node.name == tag.getSubjectName() }
- ?.detailOrNull(NodeKind.Type)
- }
- }
- "return" -> {
- section.appendTypeElement(signature) { it.detailOrNull(NodeKind.Type) }
- }
- }
- section.appendAll(convertJavadocElements(tag.contentElements(), context))
- }
- }
- }
-
- docComment.tags.forEach { tag ->
- when (tag.name) {
- "see" -> result.convertSeeTag(tag)
- "deprecated" -> {
- deprecatedContent = Content().apply {
- appendAll(convertJavadocElements(tag.contentElements(), element))
- }
- }
- in tagsToInherit -> {}
- else -> {
- val subjectName = tag.getSubjectName()
- val section = result.addSection(javadocSectionDisplayName(tag.name), subjectName)
-
- section.appendAll(convertJavadocElements(tag.contentElements(), element))
- }
- }
- }
- return JavadocParseResult(result, deprecatedContent)
- }
-
- private val tagsToInherit = setOf("param", "return", "throws")
-
- private data class TagWithContext(val tag: PsiDocTag, val context: PsiNamedElement)
-
- private fun PsiMethod.searchInheritedTags(): Map<String, Collection<TagWithContext>> {
-
- val output = tagsToInherit.keysToMap { mutableMapOf<String?, TagWithContext>() }
-
- fun recursiveSearch(methods: Array<PsiMethod>) {
- for (method in methods) {
- recursiveSearch(method.findSuperMethods())
- }
- for (method in methods) {
- for (tag in method.docComment?.tags.orEmpty()) {
- if (tag.name in tagsToInherit) {
- output[tag.name]!![tag.getSubjectName()] = TagWithContext(tag, method)
- }
- }
- }
- }
-
- recursiveSearch(arrayOf(this))
- return output.mapValues { it.value.values }
- }
-
-
- private fun PsiDocTag.contentElements(): Iterable<PsiElement> {
- val tagValueElements = children
- .dropWhile { it.node?.elementType == JavaDocTokenType.DOC_TAG_NAME }
- .dropWhile { it is PsiWhiteSpace }
- .filterNot { it.node?.elementType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS }
- return if (getSubjectName() != null) tagValueElements.dropWhile { it is PsiDocTagValue } else tagValueElements
- }
-
- private fun convertJavadocElements(elements: Iterable<PsiElement>, element: PsiNamedElement): List<ContentNode> {
- val doc = Jsoup.parse(expandAllForElements(elements, element))
- return doc.body().childNodes().mapNotNull {
- convertHtmlNode(it)
- }
- }
-
- private fun ContentBlock.appendAll(nodes: List<ContentNode>) {
- nodes.forEach { append(it) }
- }
-
- private fun expandAllForElements(elements: Iterable<PsiElement>, element: PsiNamedElement): String {
- val htmlBuilder = StringBuilder()
- elements.forEach {
- if (it is PsiInlineDocTag) {
- htmlBuilder.append(convertInlineDocTag(it, element))
- } else {
- htmlBuilder.append(it.text)
- }
- }
- return htmlBuilder.toString().trim()
- }
-
- private fun convertHtmlNode(node: Node, insidePre: Boolean = false): ContentNode? {
- if (node is TextNode) {
- val text = if (insidePre) node.wholeText else node.text()
- return ContentText(text)
- } else if (node is Element) {
- val childBlock = createBlock(node, insidePre)
-
- node.childNodes().forEach {
- val child = convertHtmlNode(it, insidePre || childBlock is ContentBlockCode)
- if (child != null) {
- childBlock.append(child)
- }
- }
- return childBlock
- }
- return null
- }
-
- private fun createBlock(element: Element, insidePre: Boolean): ContentBlock = when (element.tagName()) {
- "p" -> ContentParagraph()
- "b", "strong" -> ContentStrong()
- "i", "em" -> ContentEmphasis()
- "s", "del" -> ContentStrikethrough()
- "code" -> if (insidePre) ContentBlock() else ContentCode()
- "pre" -> ContentBlockCode()
- "ul" -> ContentUnorderedList()
- "ol" -> ContentOrderedList()
- "li" -> ContentListItem()
- "a" -> createLink(element)
- "br" -> ContentBlock().apply { hardLineBreak() }
- else -> ContentBlock()
- }
-
- private fun createLink(element: Element): ContentBlock {
- return when {
- element.hasAttr("docref") -> {
- val docref = element.attr("docref")
- ContentNodeLazyLink(docref) { refGraph.lookupOrWarn(docref, logger)}
- }
- element.hasAttr("href") -> {
- val href = element.attr("href")
-
- val uri = try {
- URI(href)
- } catch (_: Exception) {
- null
- }
-
- if (uri?.isAbsolute == false) {
- ContentLocalLink(href)
- } else {
- ContentExternalLink(href)
- }
- }
- element.hasAttr("name") -> {
- ContentBookmark(element.attr("name"))
- }
- else -> ContentBlock()
- }
- }
-
- private fun MutableContent.convertSeeTag(tag: PsiDocTag) {
- val linkElement = tag.linkElement() ?: return
- val seeSection = findSectionByTag(ContentTags.SeeAlso) ?: addSection(ContentTags.SeeAlso, null)
-
- val valueElement = tag.referenceElement()
- val externalLink = resolveExternalLink(valueElement)
- val text = ContentText(linkElement.text)
-
- val linkSignature by lazy { resolveInternalLink(valueElement) }
- val node = when {
- externalLink != null -> {
- val linkNode = ContentExternalLink(externalLink)
- linkNode.append(text)
- linkNode
- }
- linkSignature != null -> {
- val linkNode =
- ContentNodeLazyLink(
- (tag.valueElement ?: linkElement).text
- ) { refGraph.lookupOrWarn(linkSignature!!, logger) }
- linkNode.append(text)
- linkNode
- }
- else -> text
- }
- seeSection.append(node)
- }
-
- private fun convertInlineDocTag(tag: PsiInlineDocTag, element: PsiNamedElement) = when (tag.name) {
- "link", "linkplain" -> {
- val valueElement = tag.referenceElement()
- val externalLink = resolveExternalLink(valueElement)
- val linkSignature by lazy { resolveInternalLink(valueElement) }
- if (externalLink != null || linkSignature != null) {
- val labelText = tag.dataElements.firstOrNull { it is PsiDocToken }?.text ?: valueElement!!.text
- val linkTarget = if (externalLink != null) "href=\"$externalLink\"" else "docref=\"$linkSignature\""
- val link = "<a $linkTarget>${labelText.htmlEscape()}</a>"
- if (tag.name == "link") "<code>$link</code>" else link
- } else if (valueElement != null) {
- valueElement.text
- } else {
- ""
- }
- }
- "code", "literal" -> {
- val text = StringBuilder()
- tag.dataElements.forEach { text.append(it.text) }
- val escaped = text.toString().trimStart().htmlEscape()
- if (tag.name == "code") "<code>$escaped</code>" else escaped
- }
- "inheritDoc" -> {
- val result = (element as? PsiMethod)?.let {
- // @{inheritDoc} is only allowed on functions
- val parent = tag.parent
- when (parent) {
- is PsiDocComment -> element.findSuperDocCommentOrWarn()
- is PsiDocTag -> element.findSuperDocTagOrWarn(parent)
- else -> null
- }
- }
- result ?: tag.text
- }
- else -> tag.text
- }
-
- private fun PsiDocTag.referenceElement(): PsiElement? =
- linkElement()?.let {
- if (it.node.elementType == JavaDocElementType.DOC_REFERENCE_HOLDER) {
- PsiTreeUtil.findChildOfType(it, PsiJavaCodeReferenceElement::class.java)
- } else {
- it
- }
- }
-
- private fun PsiDocTag.linkElement(): PsiElement? =
- valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace }
-
- private fun resolveExternalLink(valueElement: PsiElement?): String? {
- val target = valueElement?.reference?.resolve()
- if (target != null) {
- return externalDocumentationLinkResolver.buildExternalDocumentationLink(target)
- }
- return null
- }
-
- private fun resolveInternalLink(valueElement: PsiElement?): String? {
- val target = valueElement?.reference?.resolve()
- if (target != null) {
- return signatureProvider.signature(target)
- }
- return null
- }
-
- fun PsiDocTag.getSubjectName(): String? {
- if (name == "param" || name == "throws" || name == "exception") {
- return valueElement?.text
- }
- return null
- }
-
- private fun PsiMethod.findSuperDocCommentOrWarn(): String {
- val method = findFirstSuperMethodWithDocumentation(this)
- if (method != null) {
- val descriptionElements = method.docComment?.descriptionElements?.dropWhile {
- it.text.trim().isEmpty()
- } ?: return ""
-
- return expandAllForElements(descriptionElements, method)
- }
- logger.warn("No docs found on supertype with {@inheritDoc} method ${this.name} in ${this.containingFile.name}:${this.lineNumber()}")
- return ""
- }
-
-
- private fun PsiMethod.findSuperDocTagOrWarn(elementToExpand: PsiDocTag): String {
- val result = findFirstSuperMethodWithDocumentationforTag(elementToExpand, this)
-
- if (result != null) {
- val (method, tag) = result
-
- val contentElements = tag.contentElements().dropWhile { it.text.trim().isEmpty() }
-
- val expandedString = expandAllForElements(contentElements, method)
-
- return expandedString
- }
- logger.warn("No docs found on supertype for @${elementToExpand.name} ${elementToExpand.getSubjectName()} with {@inheritDoc} method ${this.name} in ${this.containingFile.name}:${this.lineNumber()}")
- return ""
- }
-
- private fun findFirstSuperMethodWithDocumentation(current: PsiMethod): PsiMethod? {
- val superMethods = current.findSuperMethods()
- for (method in superMethods) {
- val docs = method.docComment?.descriptionElements?.dropWhile { it.text.trim().isEmpty() }
- if (!docs.isNullOrEmpty()) {
- return method
- }
- }
- for (method in superMethods) {
- val result = findFirstSuperMethodWithDocumentation(method)
- if (result != null) {
- return result
- }
- }
-
- return null
- }
-
- private fun findFirstSuperMethodWithDocumentationforTag(elementToExpand: PsiDocTag, current: PsiMethod): Pair<PsiMethod, PsiDocTag>? {
- val superMethods = current.findSuperMethods()
- val mappedFilteredTags = superMethods.map {
- it to it.docComment?.tags?.filter { it.name == elementToExpand.name }
- }
-
- for ((method, tags) in mappedFilteredTags) {
- tags ?: continue
- for (tag in tags) {
- val (tagSubject, elementSubject) = when (tag.name) {
- "throws" -> {
- // match class names only for throws, ignore possibly fully qualified path
- // TODO: Always match exactly here
- tag.getSubjectName()?.split(".")?.last() to elementToExpand.getSubjectName()?.split(".")?.last()
- }
- else -> {
- tag.getSubjectName() to elementToExpand.getSubjectName()
- }
- }
-
- if (tagSubject == elementSubject) {
- return method to tag
- }
- }
- }
-
- for (method in superMethods) {
- val result = findFirstSuperMethodWithDocumentationforTag(elementToExpand, method)
- if (result != null) {
- return result
- }
- }
- return null
- }
-
-}