Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e88bd65
Introduce custom plugin's JavaDoc tags #565
onewhl Jul 22, 2022
43c4cf0
Render UtBot custom JavaDoc tags correctly #565
onewhl Jul 25, 2022
760594f
Add an option to generate summaries using custom JavaDoc tags #565
onewhl Jul 26, 2022
050550f
Fill value of utbot.iterates tag #565
onewhl Jul 27, 2022
ca86976
Collect info about Invoke, Iterate, and Return sections #565
onewhl Jul 28, 2022
d77e739
Review fixes
onewhl Aug 1, 2022
6ae1157
Add unit tests for summaries with custom JavaDoc tags #565
onewhl Aug 2, 2022
100e3df
Fix after rebasing
onewhl Aug 3, 2022
8d4063b
Add summary tests for MinStack #565
onewhl Aug 3, 2022
6d01afe
Fix broken tests
onewhl Aug 4, 2022
d03ad76
Add <pre> tag only in case when custom javadoc tags are not used
onewhl Aug 4, 2022
74c5822
Use a full exception name instead of simple name to build inline link…
onewhl Aug 6, 2022
7e4aaf9
Minor refactoring
onewhl Aug 6, 2022
92eb15d
Minor refactoring: avoid code duplication
onewhl Aug 6, 2022
cf9b60f
Add DocCustomTagStatement and CgCustomTagStatement
onewhl Aug 6, 2022
9c14eb0
Refactored code to avoid code duplication
onewhl Aug 6, 2022
6dce850
Fix tests: add full name for classes
onewhl Aug 6, 2022
7800d33
Add JUnit extension to control USE_CUSTOM_TAGS setting
onewhl Aug 6, 2022
d003962
Move useCustomJavaDocTags to UtSettings, make useFuzzing true
onewhl Aug 7, 2022
f8e6764
Remove unused import and fix broken tests
onewhl Aug 7, 2022
321bf0d
Fix broken tests
onewhl Aug 8, 2022
a52fb20
Add comments, remove unused method
onewhl Aug 8, 2022
83f4e71
Review fixes: fixed formatting, removed redundant types
onewhl Aug 9, 2022
abe7ca6
Review fixes: fixed formatting, removed useless overriding methods
onewhl Aug 15, 2022
475cae5
Review fixes: extracted method, polished code
onewhl Aug 15, 2022
99b06ad
fix after rebasing
onewhl Aug 17, 2022
21a7035
fix after rebasing
onewhl Aug 23, 2022
4951b6f
review fixes
onewhl Aug 23, 2022
bd616a9
fix rendering after updating to idea 2022.1. now we don't need to gen…
onewhl Aug 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Render UtBot custom JavaDoc tags correctly #565
  • Loading branch information
onewhl committed Aug 24, 2022
commit 43c4cf0913bc4b4d928e62847b66bb939b6bd204
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ class UtCustomJavaDocTagProvider : CustomJavadocTagProvider {
UtCustomTag.ThrowsException,
)

sealed class UtCustomTag(private val name: String) : JavadocTagInfo {
sealed class UtCustomTag(private val name: String, private val message: String) : JavadocTagInfo {
override fun getName(): String = name

fun getMessage(): String = message

override fun isInline() = false

override fun checkTagValue(value: PsiDocTagValue?): String? = null
Expand All @@ -36,13 +38,13 @@ class UtCustomJavaDocTagProvider : CustomJavadocTagProvider {
return element is PsiMethod
}

object ClassUnderTest : UtCustomTag("utbot.classUnderTest")
object MethodUnderTest : UtCustomTag("utbot.methodUnderTest")
object ExpectedResult : UtCustomTag("utbot.expectedResult")
object ActualResult : UtCustomTag("utbot.actualResult")
object Executes : UtCustomTag("utbot.executes")
object Invokes : UtCustomTag("utbot.invokes")
object ReturnsFrom : UtCustomTag("utbot.returnsFrom")
object ThrowsException : UtCustomTag("utbot.throwsException")
object ClassUnderTest : UtCustomTag("utbot.classUnderTest", "Class under test")
object MethodUnderTest : UtCustomTag("utbot.methodUnderTest", "Method under test")
object ExpectedResult : UtCustomTag("utbot.expectedResult", "Expected result")
object ActualResult : UtCustomTag("utbot.actualResult", "Actual result")
object Executes : UtCustomTag("utbot.executes", "Executes")
object Invokes : UtCustomTag("utbot.invokes", "Invokes")
object ReturnsFrom : UtCustomTag("utbot.returnsFrom", "Returns from")
object ThrowsException : UtCustomTag("utbot.throwsException", "Throws exception")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.utbot.intellij.plugin.javadoc

import com.intellij.codeInsight.javadoc.JavaDocExternalFilter
import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator
import com.intellij.lang.java.JavaDocumentationProvider
import com.intellij.psi.PsiDocCommentBase
import com.intellij.psi.PsiJavaDocumentedElement
import com.intellij.psi.javadoc.PsiDocComment

/**
* To render UtBot custom JavaDoc tags correctly, we need to override the way it generates HTML tags for comments.
* We get JavaDoc info generated by IJ platform and include sections related to UTBot,
* each section relates to the specific JavaDoc tag.
* It renders text, code, and links.
*/
class UtDocumentationProvider : JavaDocumentationProvider() {
override fun generateRenderedDoc(comment: PsiDocCommentBase): String {
var target = comment.owner
if (target == null) target = comment
val docComment: PsiDocComment?
var finalJavaDoc = ""
if (target is PsiJavaDocumentedElement) {
docComment = target.docComment
if (docComment != null) {
val baseJavaDocInfoGenerator = JavaDocInfoGenerator(target.project, target)
val baseJavaDocInfo = baseJavaDocInfoGenerator.generateRenderedDocInfo()
val utJavaDocInfoGenerator = UtJavaDocInfoGenerator()
val javaDocInfoWithUtSections =
utJavaDocInfoGenerator.addUtBotSpecificSectionsToJavaDoc(baseJavaDocInfo, docComment)
finalJavaDoc = JavaDocExternalFilter.filterInternalDocInfo(javaDocInfoWithUtSections)!!
}
}
return finalJavaDoc
}
Comment thread
onewhl marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package org.utbot.intellij.plugin.javadoc

import com.intellij.codeInsight.documentation.DocumentationManagerUtil
import com.intellij.codeInsight.javadoc.JavaDocUtil
import com.intellij.lang.documentation.DocumentationMarkup
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
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 mu.KotlinLogging

private const val LINK_TAG = "link"
private const val LINKPLAIN_TAG = "linkplain"
private const val LITERAL_TAG = "literal"
private const val CODE_TAG = "code"
private const val SYSTEM_PROPERTY_TAG = "systemProperty"
private const val MESSAGE_SEPARATOR = ":"

private val logger = KotlinLogging.logger {}

class UtJavaDocInfoGenerator {
/**
* Generates UtBot specific sections to include them to rendered JavaDoc comment.
*/
fun addUtBotSpecificSectionsToJavaDoc(javadoc: String?, comment: PsiDocComment): String {
val builder: StringBuilder = StringBuilder(javadoc)
generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider.UtCustomTag.ClassUnderTest)
generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider.UtCustomTag.MethodUnderTest)
generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider.UtCustomTag.Invokes)
generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider.UtCustomTag.Executes)
generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider.UtCustomTag.ExpectedResult)
generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider.UtCustomTag.ActualResult)
generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider.UtCustomTag.ReturnsFrom)
generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider.UtCustomTag.ThrowsException)
return builder.toString()
}

/**
* Searches for UtBot tag in the comment and generates a related section for it.
*/
private fun generateUtTagSection(
builder: StringBuilder,
comment: PsiDocComment?,
Comment thread
onewhl marked this conversation as resolved.
Outdated
utTag: UtCustomJavaDocTagProvider.UtCustomTag
) {
if (comment != null) {
val tag = comment.findTagByName(utTag.name) ?: return
startHeaderSection(builder, utTag.getMessage())?.append("<p>")
val tmp = StringBuilder()
generateValue(tmp, tag.dataElements)
builder.append(tmp.toString().trim { it <= ' ' })
Comment thread
onewhl marked this conversation as resolved.
Outdated
builder.append(DocumentationMarkup.SECTION_END)
}
}

private fun startHeaderSection(builder: StringBuilder, message: String): StringBuilder? {
return builder.append(DocumentationMarkup.SECTION_HEADER_START)
.append(message)
.append(MESSAGE_SEPARATOR)
.append(DocumentationMarkup.SECTION_SEPARATOR)
}

/**
* Generates info depending on tag's value type.
*/
private fun generateValue(builder: StringBuilder, elements: Array<PsiElement>) {
var offset = if (elements.isNotEmpty()) {
elements[0].textOffset + elements[0].text.length
} else 0
Comment thread
onewhl marked this conversation as resolved.
Outdated

for (i in elements.indices) {
if (elements[i].textOffset > offset) builder.append(' ')
offset = elements[i].textOffset + elements[i].text.length
val element = elements[i]
if (element is PsiInlineDocTag) {
when (element.name) {
LITERAL_TAG -> generateLiteralValue(builder, element)
CODE_TAG, SYSTEM_PROPERTY_TAG -> generateCodeValue(element, builder)
LINK_TAG -> generateLinkValue(element, builder, false)
LINKPLAIN_TAG -> generateLinkValue(element, builder, true)
}
} else {
appendPlainText(builder, element.text)
}
}
Comment thread
onewhl marked this conversation as resolved.
Outdated
}

private fun appendPlainText(builder: StringBuilder, text: String) {
builder.append(StringUtil.replaceUnicodeEscapeSequences(text))
}

private fun collectElementText(builder: StringBuilder, element: PsiElement) {
element.accept(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
super.visitElement(element)
if (element is PsiWhiteSpace ||
element is PsiJavaToken ||
element is PsiDocToken && element.tokenType !== JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS
Comment thread
onewhl marked this conversation as resolved.
Outdated
) {
builder.append(element.text)
}
}
})
}

private fun generateCodeValue(tag: PsiInlineDocTag, builder: StringBuilder) {
builder.append("<code>")
val pos = builder.length
generateLiteralValue(builder, tag)
builder.append("</code>")
if (builder[pos] == '\n') builder.insert(
pos,
' '
) // line break immediately after opening tag is ignored by JEditorPane
Comment thread
onewhl marked this conversation as resolved.
Outdated
}

private fun generateLiteralValue(builder: StringBuilder, tag: PsiDocTag) {
val tmpBuilder = StringBuilder()
val children = tag.children
for (i in 2 until children.size - 1) { // process all children except tag opening/closing elements
val child = children[i]
if (child is PsiDocToken && child.tokenType === JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) continue
var elementText = child.text
if (child is PsiWhiteSpace) {
val pos = elementText.lastIndexOf('\n')
if (pos >= 0) elementText = elementText.substring(0, pos + 1) // skip whitespace before leading asterisk
}
appendPlainText(tmpBuilder, StringUtil.escapeXmlEntities(elementText))
}
builder.append(StringUtil.trimLeading(tmpBuilder))
}

private fun generateLinkValue(tag: PsiInlineDocTag, builder: StringBuilder, plainLink: Boolean) {
val tagElements = tag.dataElements
val linkText: String = createLinkText(tagElements)
Comment thread
onewhl marked this conversation as resolved.
Outdated
if (linkText.isNotEmpty()) {
Comment thread
onewhl marked this conversation as resolved.
Outdated
val index = JavaDocUtil.extractReference(linkText)
val referenceText = linkText.substring(0, index).trim { it <= ' ' }
val label = StringUtil.nullize(linkText.substring(index).trim { it <= ' ' })
Comment thread
onewhl marked this conversation as resolved.
Outdated
generateLink(builder, referenceText, label, tagElements[0], plainLink)
}
}

private fun createLinkText(tagElements: Array<PsiElement>): String {
var offset = if (tagElements.isNotEmpty()) {
Comment thread
onewhl marked this conversation as resolved.
Outdated
tagElements[0].textOffset + tagElements[0].text.length
} else {
0
}

val builder = StringBuilder()
for (i in tagElements.indices) {
val tagElement = tagElements[i]
if (tagElement.textOffset > offset) builder.append(' ')
offset = tagElement.textOffset + tagElement.text.length
collectElementText(builder, tagElement)
if (i < tagElements.size - 1) {
builder.append(' ')
}
}
return builder.toString().trim { it <= ' ' }
}

private fun generateLink(
builder: StringBuilder,
refText: String?,
label: String?,
context: PsiElement,
plainLink: Boolean
) {
var linkLabel = label
if (label == null) {
val manager = context.manager
linkLabel = JavaDocUtil.getLabelText(manager.project, manager, refText, context)
}
Comment thread
onewhl marked this conversation as resolved.
Outdated

var target: PsiElement? = null
try {
if (refText != null) {
target = JavaDocUtil.findReferenceTarget(context.manager, refText, context)
}
} catch (e: IndexNotReadyException) {
logger.info(e) { "Failed to find a reference while generating JavaDoc comment. Details: ${e.message}" }
Comment thread
onewhl marked this conversation as resolved.
Outdated
}

if (target == null && DumbService.isDumb(context.project)) {
builder.append(linkLabel)
} else if (target == null) {
builder.append("<font color=red>").append(linkLabel).append("</font>")
} else {
val referenceText = JavaDocUtil.getReferenceText(target.project, target)
if (referenceText != null) {
DocumentationManagerUtil.createHyperlink(builder, target, referenceText, linkLabel, plainLink)
}
Comment thread
onewhl marked this conversation as resolved.
Outdated
}
}
}
1 change: 1 addition & 0 deletions utbot-intellij/src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<projectModelModifier implementation="org.utbot.intellij.plugin.util.UtProjectModelModifier"/>
<!--Documentation-->
<customJavadocTagProvider implementation="org.utbot.intellij.plugin.javadoc.UtCustomJavaDocTagProvider"/>
<lang.documentationProvider language="JAVA" order="first" implementationClass="org.utbot.intellij.plugin.javadoc.UtDocumentationProvider"/>
</extensions>

<!-- Minimum and maximum build of IDE compatible with the plugin -->
Expand Down