Skip to content

Older versions of IDE support #798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/build-and-run-tests-from-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
fail-fast: false
matrix: ${{ fromJson(needs.prepare-tests-matrix.outputs.matrix) }}
runs-on: ubuntu-20.04
container: unittestbot/java-env:java11-zulu-jdk-fx-gradle7.4.2-kotlinc1.7.0
container: unittestbot/java-env:java11-zulu-jdk-gradle7.4.2-kotlinc1.7.0
steps:
- name: Print environment variables
run: printenv
Expand Down Expand Up @@ -111,7 +111,7 @@ jobs:
matrix:
project: [utbot-api, utbot-cli, utbot-core, utbot-framework-api, utbot-fuzzers, utbot-gradle, utbot-instrumentation, utbot-instrumentation-tests, utbot-intellij, utbot-junit-contest, utbot-sample, utbot-summary, utbot-summary-tests]
runs-on: ubuntu-20.04
container: unittestbot/java-env:java11-zulu-jdk-fx-gradle7.4.2-kotlinc1.7.0
container: unittestbot/java-env:java11-zulu-jdk-gradle7.4.2-kotlinc1.7.0
steps:
- name: Print environment variables
run: printenv
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-and-run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
needs: build-and-run-tests
if: ${{ github.event_name == 'push' }}
runs-on: ubuntu-20.04
container: unittestbot/java-env:java11-zulu-jdk-fx-gradle7.4.2-kotlinc1.7.0
container: unittestbot/java-env:java11-zulu-jdk-gradle7.4.2-kotlinc1.7.0
steps:
- name: Print environment variables
run: printenv
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-plugin-and-cli-from-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ env:
jobs:
publish_plugin_and_cli:
runs-on: ubuntu-20.04
container: unittestbot/java-env:java11-zulu-jdk-fx-gradle7.4.2-kotlinc1.7.0
container: unittestbot/java-env:java11-zulu-jdk-gradle7.4.2-kotlinc1.7.0

steps:
- name: Print environment variables
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/run-chosen-tests-from-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ env:
jobs:
run-chosen-tests:
runs-on: ubuntu-20.04
container: unittestbot/java-env:java11-zulu-jdk-fx-gradle7.4.2-kotlinc1.7.0
container: unittestbot/java-env:java11-zulu-jdk-gradle7.4.2-kotlinc1.7.0

steps:
- name: Print environment variables
Expand Down
6 changes: 2 additions & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,5 @@ shadow_jar_version=7.1.2

org.gradle.daemon=false
org.gradle.parallel=false
org.gradle.jvmargs="-XX:MaxHeapSize=3072m"
kotlin.compiler.execution.strategy=in-process

org.gradle.jvmargs=-Xmx6144m
org.gradle.jvmargs="-XX:MaxHeapSize=6144m"
kotlin.compiler.execution.strategy=in-process
2 changes: 1 addition & 1 deletion utbot-intellij/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ dependencies {

// See https://github.com/JetBrains/gradle-intellij-plugin/
intellij {
version = "2022.1"
version = "2021.2"
type = "IC"
plugins = ['java',
// TODO: SAT-1539 - specify version of android plugin to be supported by our kotlin version.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
package org.utbot.intellij.plugin.javadoc

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

/**
* To render UtBot custom JavaDoc tags messages, we need to override basic behaviour of [JavaDocumentationProvider].
* The IJ platform knows only custom tag names, so we need to add their messages in rendered comments to make it look nice.
*/
class UtDocumentationProvider : JavaDocumentationProvider() {

override fun generateRenderedDoc(comment: PsiDocCommentBase): String? {
val target = comment.owner ?: comment

if (target !is PsiJavaDocumentedElement) {
return ""
}

val baseJavaDocInfoGenerator = JavaDocInfoGeneratorFactory.getBuilder(target.getProject())
.setPsiElement(target)
.setIsGenerationForRenderedDoc(true)
.create()
val docComment = target.docComment ?: return ""

val baseJavaDocInfoGenerator = JavaDocInfoGenerator(target.project, target)

// get JavaDoc comment rendered by the platform.
val baseJavaDocInfo = baseJavaDocInfoGenerator.generateRenderedDocInfo()

val finalDocContent = replaceTagNamesWithMessages(baseJavaDocInfoGenerator.generateRenderedDocInfo())
// add UTBot sections with custom tags.
val utJavaDocInfoGenerator = UtJavaDocInfoGenerator()
val javaDocInfoWithUtSections =
utJavaDocInfoGenerator.addUtBotSpecificSectionsToJavaDoc(baseJavaDocInfo, docComment)

return JavaDocExternalFilter.filterInternalDocInfo(finalDocContent)
return JavaDocExternalFilter.filterInternalDocInfo(javaDocInfoWithUtSections)
}

/**
* Replaces names of plugin's custom JavaDoc tags with their messages in the comment generated by the IJ platform.
* Example: utbot.MethodUnderTest -> Method under test.
* Example: utbot.methodUnderTest -> Method under test.
*
* Use it to update comment built by the IJ platform after updating to 2022.2.
*/
private fun replaceTagNamesWithMessages(comment: String?) =
comment?.let {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
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 const val PARAGRAPH_TAG = "<p>"
private const val CODE_TAG_START = "<code>"
private const val CODE_TAG_END = "</code>"

private val logger = KotlinLogging.logger {}

/**
* Generates UtBot specific sections to include them to rendered JavaDoc comment.
*
* Methods responsible for value generation were taken from IJ platform class (they are private and couldn't be used outside).
*
* See [com.intellij.codeInsight.javadoc.JavaDocInfoGenerator].
*
* It wouldn't be needed to generate rendered doc on our own after updating to the IJ platform 2022.2,
* so delete it after updating and use basic [com.intellij.codeInsight.javadoc.JavaDocInfoGenerator].
*/
class UtJavaDocInfoGenerator {
fun addUtBotSpecificSectionsToJavaDoc(javadoc: String?, comment: PsiDocComment): String {
val builder = if (javadoc == null) {
StringBuilder()
} else {
StringBuilder(javadoc)
}

val docTagProvider = UtCustomJavaDocTagProvider()
docTagProvider.supportedTags.forEach {
generateUtTagSection(builder, comment, it)
}
return builder.toString()
}

/**
* Searches for UtBot tag in the comment and generates a related section for it.
*/
private fun generateUtTagSection(
builder: StringBuilder,
comment: PsiDocComment,
utTag: UtCustomJavaDocTagProvider.UtCustomTagInfo
) {
val tag = comment.findTagByName(utTag.name) ?: return
startHeaderSection(builder, utTag.getMessage()).append(PARAGRAPH_TAG)
val sectionContent = buildString {
generateValue(this, tag.dataElements)
trim()
}

builder.append(sectionContent)
builder.append(DocumentationMarkup.SECTION_END)
}

private fun startHeaderSection(builder: StringBuilder, message: String): StringBuilder =
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>) {
if (elements.isEmpty()) {
return
}

var offset = elements[0].textOffset + elements[0].text.length

for (element in elements) {
with(element) {
if (textOffset > offset) {
builder.append(' ')
}

offset = textOffset + text.length

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, text)
}
}
}
}

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
) {
builder.append(element.text)
}
}
})
}

private fun generateCodeValue(tag: PsiInlineDocTag, builder: StringBuilder) {
builder.append(CODE_TAG_START)
val pos = builder.length
generateLiteralValue(builder, tag)
builder.append(CODE_TAG_END)
if (builder[pos] == '\n') {
builder.insert(
pos,
' '
) // line break immediately after opening tag is ignored by JEditorPane
}
}

private fun generateLiteralValue(builder: StringBuilder, tag: PsiDocTag) {
val literalValue = buildString {
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(this, StringUtil.escapeXmlEntities(elementText))
}
}
builder.append(StringUtil.trimLeading(literalValue))
}

private fun generateLinkValue(tag: PsiInlineDocTag, builder: StringBuilder, plainLink: Boolean) {
val tagElements = tag.dataElements
val linkText = createLinkText(tagElements)
if (linkText.isNotEmpty()) {
val index = JavaDocUtil.extractReference(linkText)
val referenceText = linkText.substring(0, index).trim()
val label = StringUtil.nullize(linkText.substring(index).trim())
generateLink(builder, referenceText, label, tagElements[0], plainLink)
}
}

private fun createLinkText(tagElements: Array<PsiElement>): String {
var offset = if (tagElements.isNotEmpty()) {
tagElements[0].textOffset + tagElements[0].text.length
} else {
0
}

return buildString {
for (i in tagElements.indices) {
val tagElement = tagElements[i]
if (tagElement.textOffset > offset) {
this.append(' ')
}
offset = tagElement.textOffset + tagElement.text.length
collectElementText(this, tagElement)
if (i < tagElements.lastIndex) {
this.append(' ')
}
}
}.trim()
}

private fun generateLink(
builder: StringBuilder,
refText: String?,
label: String?,
context: PsiElement,
plainLink: Boolean
) {
val linkLabel = label ?: context.manager.let {
JavaDocUtil.getLabelText(it.project, it, refText, context)
}

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}" }
}

if (target == null && DumbService.isDumb(context.project)) {
builder.append(linkLabel)
} else if (target == null) {
builder.append("<font color=red>").append(linkLabel).append("</font>")
} else {
JavaDocUtil.getReferenceText(target.project, target)?.let {
DocumentationManagerUtil.createHyperlink(builder, target, it, linkLabel, plainLink)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.childrenOfType
import org.jetbrains.plugins.groovy.lang.psi.util.childrenOfType
import com.intellij.testFramework.MapDataContext
import mu.KotlinLogging
import org.utbot.intellij.plugin.models.GenerateTestsModel
Expand Down