Override/Implement: Implement JavaDoc/KDoc copying

#KT-8682 Fixed
 #KT-12735 Fixed
(cherry picked from commit f4486be)
This commit is contained in:
Alexey Sedunov
2016-06-20 14:41:00 +03:00
parent 3f09b7f9d5
commit ffe4e760d7
15 changed files with 112 additions and 12 deletions
+1
View File
@@ -14,6 +14,7 @@
<orderEntry type="module" module-name="light-classes" />
<orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="j2k" />
<orderEntry type="library" scope="PROVIDED" name="gradle-and-groovy-plugin" level="project" />
</component>
</module>
@@ -72,18 +72,22 @@ abstract class OverrideImplementMembersHandler : LanguageCodeInsightActionHandle
return
}
val selectedElements = if (implementAll) {
members
val copyDoc: Boolean
val selectedElements: Collection<OverrideMemberChooserObject>
if (implementAll) {
selectedElements = members
copyDoc = false
}
else {
val chooser = showOverrideImplementChooser(project, members.toTypedArray()) ?: return
chooser.selectedElements ?: return
selectedElements = chooser.selectedElements ?: return
copyDoc = chooser.isCopyJavadoc
}
if (selectedElements.isEmpty()) return
PsiDocumentManager.getInstance(project).commitAllDocuments()
generateMembers(editor, classOrObject, selectedElements)
generateMembers(editor, classOrObject, selectedElements, copyDoc)
}
override fun invoke(project: Project, editor: Editor, file: PsiFile) {
@@ -93,9 +97,14 @@ abstract class OverrideImplementMembersHandler : LanguageCodeInsightActionHandle
override fun startInWriteAction(): Boolean = false
companion object {
fun generateMembers(editor: Editor?, classOrObject: KtClassOrObject, selectedElements: Collection<OverrideMemberChooserObject>) {
fun generateMembers(
editor: Editor?,
classOrObject: KtClassOrObject,
selectedElements: Collection<OverrideMemberChooserObject>,
copyDoc: Boolean
) {
val project = classOrObject.project
insertMembersAfter(editor, classOrObject, selectedElements.map { it.generateMember(project) })
insertMembersAfter(editor, classOrObject, selectedElements.map { it.generateMember(project, copyDoc) })
}
}
}
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.generation.ClassMember
import com.intellij.codeInsight.generation.MemberChooserObject
import com.intellij.codeInsight.generation.MemberChooserObjectBase
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocCommentOwner
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
@@ -27,8 +28,11 @@ import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.TemplateKind
import org.jetbrains.kotlin.idea.core.getFunctionBodyTextFromTemplate
import org.jetbrains.kotlin.idea.core.util.DescriptorMemberChooserObject
import org.jetbrains.kotlin.idea.j2k.IdeaDocCommentConverter
import org.jetbrains.kotlin.idea.kdoc.KDocElementFactory
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.findDocComment.findDocComment
import org.jetbrains.kotlin.renderer.*
import org.jetbrains.kotlin.resolve.descriptorUtil.setSingleOverridden
@@ -88,15 +92,33 @@ interface OverrideMemberChooserObject : ClassMember {
}
}
fun OverrideMemberChooserObject.generateMember(project: Project): KtCallableDeclaration {
fun OverrideMemberChooserObject.generateMember(project: Project, copyDoc: Boolean): KtCallableDeclaration {
val descriptor = immediateSuper
if (preferConstructorParameter && descriptor is PropertyDescriptor) return generateConstructorParameter(project, descriptor)
return when (descriptor) {
val newMember: KtCallableDeclaration = when (descriptor) {
is SimpleFunctionDescriptor -> generateFunction(project, descriptor, bodyType)
is PropertyDescriptor -> generateProperty(project, descriptor, bodyType)
else -> error("Unknown member to override: $descriptor")
}
if (copyDoc) {
val superDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor)
val kDoc = when (superDeclaration) {
is KtDeclaration ->
findDocComment(superDeclaration)
is PsiDocCommentOwner -> {
val kDocText = superDeclaration.docComment?.let { IdeaDocCommentConverter.convertDocComment(it) }
if (kDocText.isNullOrEmpty()) null else KDocElementFactory(project).createKDocFromText(kDocText!!)
}
else -> null
}
if (kDoc != null) {
newMember.addAfter(kDoc, null)
}
}
return newMember
}
private val OVERRIDE_RENDERER = DescriptorRenderer.withOptions {
@@ -0,0 +1,217 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.j2k
import com.intellij.ide.highlighter.HtmlFileType
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.XmlRecursiveElementVisitor
import com.intellij.psi.javadoc.PsiDocComment
import com.intellij.psi.javadoc.PsiDocTag
import com.intellij.psi.javadoc.PsiDocToken
import com.intellij.psi.javadoc.PsiInlineDocTag
import com.intellij.psi.xml.XmlFile
import com.intellij.psi.xml.XmlTag
import com.intellij.psi.xml.XmlText
import com.intellij.psi.xml.XmlTokenType
import org.jetbrains.kotlin.j2k.DocCommentConverter
import org.jetbrains.kotlin.j2k.content
object IdeaDocCommentConverter : DocCommentConverter {
override fun convertDocComment(docComment: PsiDocComment): String {
val html = buildString {
appendJavadocElements(docComment.descriptionElements)
tagsLoop@
for (tag in docComment.tags) {
when (tag.name) {
"deprecated" -> continue@tagsLoop
"see" -> append("@see ${convertJavadocLink(tag.content())}\n")
else -> appendJavadocElements(tag.children).append("\n")
}
}
}
if (html.trim().isEmpty() && docComment.findTagByName("deprecated") != null) {
// @deprecated was the only content of the doc comment; we can drop the comment
return ""
}
val htmlFile = PsiFileFactory.getInstance(docComment.project).createFileFromText(
"javadoc.html", HtmlFileType.INSTANCE, html)
val htmlToMarkdownConverter = HtmlToMarkdownConverter()
htmlFile.accept(htmlToMarkdownConverter)
return htmlToMarkdownConverter.result
}
private fun StringBuilder.appendJavadocElements(elements: Array<PsiElement>): StringBuilder {
elements.forEach {
if (it is PsiInlineDocTag) {
append(convertInlineDocTag(it))
}
else {
append(it.text)
}
}
return this
}
private fun convertInlineDocTag(tag: PsiInlineDocTag) = when(tag.name) {
"code", "literal" -> {
val text = tag.dataElements.joinToString("") { it.text }
val escaped = StringUtil.escapeXml(text.trimStart())
if (tag.name == "code") "<code>$escaped</code>" else escaped
}
"link", "linkplain" -> {
val valueElement = tag.linkElement()
val labelText = tag.dataElements.firstOrNull { it is PsiDocToken }?.text ?: ""
val kdocLink = convertJavadocLink(valueElement?.text)
val linkText = if (labelText.isEmpty()) kdocLink else StringUtil.escapeXml(labelText)
"<a docref=\"$kdocLink\">$linkText</a>"
}
else -> tag.text
}
private fun convertJavadocLink(link: String?): String =
if (link != null) link.substringBefore('(').replace('#', '.') else ""
private fun PsiDocTag.linkElement(): PsiElement? =
valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace }
private class HtmlToMarkdownConverter() : XmlRecursiveElementVisitor() {
private enum class ListType { Ordered, Unordered; }
data class MarkdownSpan(val prefix: String, val suffix: String) {
companion object {
val Empty = MarkdownSpan("", "")
fun wrap(text: String) = MarkdownSpan(text, text)
fun prefix(text: String) = MarkdownSpan(text, "")
}
}
val result: String
get() = markdownBuilder.toString()
private val markdownBuilder = StringBuilder("/**")
private var afterLineBreak = false
private var whitespaceIsPartOfText = true
private var currentListType = ListType.Unordered
override fun visitWhiteSpace(space: PsiWhiteSpace) {
super.visitWhiteSpace(space)
if (whitespaceIsPartOfText) {
appendPendingText()
markdownBuilder.append(space.text)
if (space.textContains('\n')) {
afterLineBreak = true
}
}
}
override fun visitElement(element: PsiElement) {
super.visitElement(element)
val tokenType = element.node.elementType
if (tokenType == XmlTokenType.XML_DATA_CHARACTERS || tokenType == XmlTokenType.XML_CHAR_ENTITY_REF) {
appendPendingText()
markdownBuilder.append(element.text)
}
}
override fun visitXmlTag(tag: XmlTag) {
withWhitespaceAsPartOfText(false) {
val oldListType = currentListType
val atLineStart = afterLineBreak
appendPendingText()
val (openingMarkdown, closingMarkdown) = getMarkdownForTag(tag, atLineStart)
markdownBuilder.append(openingMarkdown)
super.visitXmlTag(tag)
markdownBuilder.append(closingMarkdown)
currentListType = oldListType
}
}
override fun visitXmlText(text: XmlText) {
withWhitespaceAsPartOfText(true) {
super.visitXmlText(text)
}
}
private inline fun withWhitespaceAsPartOfText(newValue: Boolean, block: () -> Unit) {
val oldValue = whitespaceIsPartOfText
whitespaceIsPartOfText = newValue
try {
block()
}
finally {
whitespaceIsPartOfText = oldValue
}
}
private fun getMarkdownForTag(tag: XmlTag, atLineStart: Boolean): MarkdownSpan = when(tag.name) {
"b", "strong" -> MarkdownSpan.wrap("**")
"p" -> if (atLineStart) MarkdownSpan.prefix("\n * ") else MarkdownSpan.prefix("\n *\n *")
"i", "em" -> MarkdownSpan.wrap("*")
"s", "del" -> MarkdownSpan.wrap("~~")
"code" -> MarkdownSpan.wrap("`")
"a" -> {
if (tag.getAttributeValue("docref") != null) {
val docRef = tag.getAttributeValue("docref")
val innerText = tag.value.text
if (docRef == innerText) MarkdownSpan("[", "]") else MarkdownSpan("[", "][$docRef]")
}
else {
MarkdownSpan("[", "](${tag.getAttributeValue("href")})")
}
}
"ul" -> { currentListType = ListType.Unordered; MarkdownSpan.Empty }
"ol" -> { currentListType = ListType.Ordered; MarkdownSpan.Empty }
"li" -> if (currentListType == ListType.Unordered) MarkdownSpan.prefix(" * ") else MarkdownSpan.prefix(" 1. ")
else -> MarkdownSpan.Empty
}
private fun appendPendingText() {
if (afterLineBreak ) {
markdownBuilder.append(" * ")
afterLineBreak = false
}
}
override fun visitXmlFile(file: XmlFile) {
super.visitXmlFile(file)
markdownBuilder.append(" */")
}
}
}