diff --git a/.github/workflows/build-and-run-tests-from-branch.yml b/.github/workflows/build-and-run-tests-from-branch.yml index 07cba5ff21..a6fb5a3bdc 100644 --- a/.github/workflows/build-and-run-tests-from-branch.yml +++ b/.github/workflows/build-and-run-tests-from-branch.yml @@ -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 @@ -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 diff --git a/.github/workflows/build-and-run-tests.yml b/.github/workflows/build-and-run-tests.yml index 2d1dc5c9a8..cf3a598e0b 100644 --- a/.github/workflows/build-and-run-tests.yml +++ b/.github/workflows/build-and-run-tests.yml @@ -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 diff --git a/.github/workflows/publish-plugin-and-cli-from-branch.yml b/.github/workflows/publish-plugin-and-cli-from-branch.yml index 7925bbd45e..8959f55e82 100644 --- a/.github/workflows/publish-plugin-and-cli-from-branch.yml +++ b/.github/workflows/publish-plugin-and-cli-from-branch.yml @@ -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 diff --git a/.github/workflows/run-chosen-tests-from-branch.yml b/.github/workflows/run-chosen-tests-from-branch.yml index 560935822a..b6b60c3097 100644 --- a/.github/workflows/run-chosen-tests-from-branch.yml +++ b/.github/workflows/run-chosen-tests-from-branch.yml @@ -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 diff --git a/gradle.properties b/gradle.properties index 49232de991..d293fa1752 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 \ No newline at end of file +org.gradle.jvmargs="-XX:MaxHeapSize=6144m" +kotlin.compiler.execution.strategy=in-process \ No newline at end of file diff --git a/utbot-intellij/build.gradle b/utbot-intellij/build.gradle index 8353c72398..4efb1e3465 100644 --- a/utbot-intellij/build.gradle +++ b/utbot-intellij/build.gradle @@ -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. diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtDocumentationProvider.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtDocumentationProvider.kt index e8b9be9757..7485cfea2d 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtDocumentationProvider.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtDocumentationProvider.kt @@ -1,16 +1,16 @@ 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 @@ -18,19 +18,26 @@ class UtDocumentationProvider : JavaDocumentationProvider() { 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 { diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtJavaDocInfoGenerator.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtJavaDocInfoGenerator.kt new file mode 100644 index 0000000000..ae597280ea --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtJavaDocInfoGenerator.kt @@ -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 = "

" +private const val CODE_TAG_START = "" +private const val CODE_TAG_END = "" + +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) { + 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): 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("").append(linkLabel).append("") + } else { + JavaDocUtil.getReferenceText(target.project, target)?.let { + DocumentationManagerUtil.createHyperlink(builder, target, it, linkLabel, plainLink) + } + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt index d478113dd9..2200534eb0 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt @@ -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