diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt index 9787deadf42..f26b939d807 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt @@ -62,7 +62,7 @@ object CheckerTestUtil { private const val IGNORE_DIAGNOSTIC_PARAMETER = "IGNORE" private const val INDIVIDUAL_DIAGNOSTIC = """(\w+;)?(\w+:)?(\w+)(?:\(((?:".*?")(?:,\s*".*?")*)\))?""" - internal val rangeStartOrEndPattern = Pattern.compile("()|()") + val rangeStartOrEndPattern = Pattern.compile("()|()") val individualDiagnosticPattern: Pattern = Pattern.compile(INDIVIDUAL_DIAGNOSTIC) fun getDiagnosticsIncludingSyntaxErrors( diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeMetaInfo/AbstractCodeMetaInfoTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeMetaInfo/AbstractCodeMetaInfoTest.kt new file mode 100644 index 00000000000..4dd3922b15a --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/codeMetaInfo/AbstractCodeMetaInfoTest.kt @@ -0,0 +1,252 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.codeMetaInfo + +import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl +import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiManager +import com.intellij.psi.impl.PsiManagerImpl +import com.intellij.psi.impl.cache.CacheManager +import com.intellij.psi.impl.source.tree.PsiErrorElementImpl +import com.intellij.psi.impl.source.tree.TreeElement +import com.intellij.psi.impl.source.tree.TreeUtil +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.UsageSearchContext +import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl +import com.intellij.testFramework.runInEdtAndWait +import com.intellij.util.io.exists +import gnu.trove.TIntArrayList +import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest +import org.jetbrains.kotlin.checkers.diagnostics.SyntaxErrorDiagnostic +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil +import org.jetbrains.kotlin.checkers.utils.DiagnosticsRenderingConfiguration +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl +import org.jetbrains.kotlin.diagnostics.AbstractDiagnostic +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.codeMetaInfo.models.CodeMetaInfoFactory +import org.jetbrains.kotlin.idea.codeMetaInfo.models.DiagnosticCodeMetaInfo +import org.jetbrains.kotlin.idea.codeMetaInfo.models.HighlightingCodeMetaInfo +import org.jetbrains.kotlin.idea.codeMetaInfo.models.ICodeMetaInfo +import org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations.AbstractCodeMetaInfoRenderConfiguration +import org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations.DiagnosticCodeMetaInfoRenderConfiguration +import org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations.HighlightingRenderConfiguration +import org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations.LineMarkerRenderConfiguration +import org.jetbrains.kotlin.idea.multiplatform.setupMppProjectFromTextFile +import org.jetbrains.kotlin.idea.resolve.getDataFlowValueFactory +import org.jetbrains.kotlin.idea.resolve.getLanguageVersionSettings +import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest +import org.jetbrains.kotlin.idea.util.sourceRoots +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.KotlinTestUtils +import java.io.File +import java.nio.file.Paths + +class CodeMetaInfoTestCase(val codeMetaInfoTypes: Collection) : DaemonAnalyzerTestCase() { + + fun getDiagnosticCodeMetaInfos( + configuration: DiagnosticCodeMetaInfoRenderConfiguration?, + parseDirective: Boolean = true + ): Collection { + val tempSourceKtFile = PsiManager.getInstance(project).findFile(file.virtualFile) as KtFile + val resolutionFacade = tempSourceKtFile.getResolutionFacade() + val (bindingContext, moduleDescriptor) = resolutionFacade.analyzeWithAllCompilerChecks(listOf(tempSourceKtFile)) + val directives = KotlinTestUtils.parseDirectives(file.text) + val diagnosticsFilter = BaseDiagnosticsTest.parseDiagnosticFilterDirective(directives, allowUnderscoreUsage = false) + val diagnostics = CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors( + bindingContext, + file, + markDynamicCalls = false, + dynamicCallDescriptors = mutableListOf(), + configuration = DiagnosticsRenderingConfiguration( + platform = null, // we don't need to attach platform-description string to diagnostic here + withNewInference = false, + languageVersionSettings = resolutionFacade.getLanguageVersionSettings(), + ), + dataFlowValueFactory = resolutionFacade.getDataFlowValueFactory(), + moduleDescriptor = moduleDescriptor as ModuleDescriptorImpl + ).map { it.diagnostic }.filter { !parseDirective || diagnosticsFilter.value(it) } + configuration?.renderParams = directives.contains(BaseDiagnosticsTest.RENDER_DIAGNOSTICS_MESSAGES) + return CodeMetaInfoFactory.getCodeMetaInfo(diagnostics, configuration) + } + + fun getLineMarkerCodeMetaInfos(configuration: LineMarkerRenderConfiguration): Collection { + if ("!CHECK_HIGHLIGHTING" in file.text) + return emptyList() + + CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, TIntArrayList().toNativeArray(), false) + val lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(getDocument(file), project) + return CodeMetaInfoFactory.getCodeMetaInfo(lineMarkers, configuration) + } + + fun getHighlightingCodeMetaInfos(configuration: HighlightingRenderConfiguration?): Collection { + val infos = CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, TIntArrayList().toNativeArray(), false) + + return CodeMetaInfoFactory.getCodeMetaInfo(infos, configuration) + } + + fun checkFile(expectedFile: File, project: Project, editor: Editor) { + myProject = project + myPsiManager = PsiManager.getInstance(myProject) as PsiManagerImpl + runInEdtAndWait { + setActiveEditor(editor) + check(expectedFile) + } + } + + fun checkFile(file: VirtualFile, expectedFile: File, project: Project) { + myProject = project + myPsiManager = PsiManager.getInstance(myProject) as PsiManagerImpl + configureByExistingFile(file) + check(expectedFile) + } + + fun check(expectedFile: File) { + + val codeMetaInfoForCheck = mutableListOf() + PsiDocumentManager.getInstance(myProject).commitAllDocuments() + + //to load text + ApplicationManager.getApplication().runWriteAction { TreeUtil.clearCaches(myFile.node as TreeElement) } + + //to initialize caches + if (!DumbService.isDumb(myProject)) { + CacheManager.SERVICE.getInstance(myProject) + .getFilesWithWord("XXX", UsageSearchContext.IN_COMMENTS, GlobalSearchScope.allScope(myProject), true) + } + + for (configuration in codeMetaInfoTypes) { + when (configuration) { + is DiagnosticCodeMetaInfoRenderConfiguration -> { + codeMetaInfoForCheck.addAll(getDiagnosticCodeMetaInfos(configuration)) + } + is HighlightingRenderConfiguration -> { + codeMetaInfoForCheck.addAll(getHighlightingCodeMetaInfos(configuration)) + } + is LineMarkerRenderConfiguration -> { + codeMetaInfoForCheck.addAll(getLineMarkerCodeMetaInfos(configuration)) + } + else -> throw IllegalArgumentException("Unexpected code meta info configuration: $configuration") + } + } + if (codeMetaInfoTypes.any { it is DiagnosticCodeMetaInfoRenderConfiguration } && + !codeMetaInfoTypes.any { it is HighlightingRenderConfiguration } + ) { + checkHighlightErrorItemsInDiagnostics( + getDiagnosticCodeMetaInfos(null, false).filterIsInstance() + ) + } + + val textWithCodeMetaInfo = CodeMetaInfoRenderer.renderTagsToText(codeMetaInfoForCheck, myEditor.document.text) + KotlinTestUtils.assertEqualsToFile( + expectedFile, + textWithCodeMetaInfo.toString() + ) + } + + private fun checkHighlightErrorItemsInDiagnostics( + diagnostics: Collection + ) { + val highlightItems: List = + getHighlightingCodeMetaInfos(null).filter { (it as HighlightingCodeMetaInfo).highlightingInfo.severity == HighlightSeverity.ERROR } + + highlightItems.forEach { highlightingCodeMetaInfo -> + assert( + diagnostics.any { diagnosticCodeMetaInfo -> + diagnosticCodeMetaInfo.start == highlightingCodeMetaInfo.start && + when (diagnosticCodeMetaInfo.diagnostic) { + is SyntaxErrorDiagnostic -> { + val diagnostic: SyntaxErrorDiagnostic = diagnosticCodeMetaInfo.diagnostic + (highlightingCodeMetaInfo as HighlightingCodeMetaInfo).highlightingInfo.description in (diagnostic.psiElement as PsiErrorElementImpl).errorDescription + } + is AbstractDiagnostic<*> -> { + val diagnostic: AbstractDiagnostic<*> = diagnosticCodeMetaInfo.diagnostic + diagnostic.factory.toString() in (highlightingCodeMetaInfo as HighlightingCodeMetaInfo).highlightingInfo.description + } + else -> throw java.lang.IllegalArgumentException("Unknown diagnostic type: ${diagnosticCodeMetaInfo.diagnostic}") + } + }, + ) { "Could not find DIAGNOSTIC for ${(highlightingCodeMetaInfo as HighlightingCodeMetaInfo).highlightingInfo}" } + } + } +} + +abstract class AbstractDiagnosticCodeMetaInfoTest : AbstractCodeMetaInfoTest() { + override fun getConfigurations() = listOf( + DiagnosticCodeMetaInfoRenderConfiguration() + ) +} + +abstract class AbstractLineMarkerCodeMetaInfoTest : AbstractCodeMetaInfoTest() { + override fun getConfigurations() = listOf( + LineMarkerRenderConfiguration() + ) +} + +abstract class AbstractHighlightingCodeMetaInfoTest : AbstractCodeMetaInfoTest() { + override fun getConfigurations() = listOf( + HighlightingRenderConfiguration() + ) +} + +abstract class AbstractCodeMetaInfoTest : AbstractMultiModuleTest() { + + open fun getConfigurations() = listOf( + DiagnosticCodeMetaInfoRenderConfiguration(), + LineMarkerRenderConfiguration(), + HighlightingRenderConfiguration() + ) + + protected open fun setupProject(testDataPath: String) { + val dependenciesTxt = File(testDataPath, "dependencies.txt") + require(dependenciesTxt.exists()) { + "${dependenciesTxt.absolutePath} does not exist. dependencies.txt is required" + } + setupMppProjectFromTextFile(File(testDataPath)) + } + + fun doTest(testDataPath: String) { + val testRoot = File(testDataPath) + val checker = CodeMetaInfoTestCase(getConfigurations()) + setupProject(testDataPath) + + for (module in ModuleManager.getInstance(project).modules) { + for (sourceRoot in module.sourceRoots) { + VfsUtilCore.processFilesRecursively(sourceRoot) { file -> + if (file.isDirectory) return@processFilesRecursively true + + checker.checkFile(file, file.findCorrespondingFileInTestDir(sourceRoot, testRoot), project) + true + } + } + } + } + + private fun VirtualFile.findCorrespondingFileInTestDir(containingRoot: VirtualFile, testDir: File): File { + val tempRootPath = Paths.get(containingRoot.path) + val tempProjectDirPath = tempRootPath.parent + val tempSourcePath = Paths.get(path) + + val relativeToProjectRootPath = tempProjectDirPath.relativize(tempSourcePath) + + val testSourcesProjectDirPath = testDir.toPath() + val testSourcePath = testSourcesProjectDirPath.resolve(relativeToProjectRootPath) + + require(testSourcePath.exists()) { + "Can't find file in testdata for copied file $this: checked at path ${testSourcePath.toAbsolutePath()}" + } + + return testSourcePath.toFile() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeMetaInfo/CodeMetaInfoRenderer.kt b/idea/tests/org/jetbrains/kotlin/idea/codeMetaInfo/CodeMetaInfoRenderer.kt new file mode 100644 index 00000000000..326bfb18b04 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/codeMetaInfo/CodeMetaInfoRenderer.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.codeMetaInfo + +import com.intellij.util.containers.Stack +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil +import org.jetbrains.kotlin.idea.codeMetaInfo.models.ICodeMetaInfo +import java.io.File + +object CodeMetaInfoRenderer { + + fun renderTagsToText( + codeMetaInfos: List, + originalText: String + ): StringBuffer { + val result = StringBuffer() + if (codeMetaInfos.isEmpty()) { + result.append(originalText) + return result + } + val sortedMetaInfos = getSortedCodeMetaInfos(codeMetaInfos) + val opened = Stack() + + for (i in originalText.indices) { + val c = originalText[i] + var prev: ICodeMetaInfo? = null + + while (!opened.isEmpty() && i == opened.peek().end) { + if (prev == null || prev.start != opened.peek().start) + closeString(result) + prev = opened.pop() + } + if (sortedMetaInfos.any { it.start == i }) { + openStartTag(result) + val matchedCodeMetaInfos = sortedMetaInfos.filter { it.start == i }.toMutableList() + val iterator = matchedCodeMetaInfos.listIterator() + var current: ICodeMetaInfo? = iterator.next() + + while (current != null) { + val next: ICodeMetaInfo? = if (iterator.hasNext()) iterator.next() else null + opened.push(current) + result.append(current.asString()) + when { + next == null -> + closeStartTag(result) + next.end == current.end -> + result.append(", ") + else -> + closeStartAndOpenNewTag(result) + } + current = next + } + } + result.append(c) + } + var prev: ICodeMetaInfo? = null + + while (!opened.isEmpty() && originalText.length == opened.peek().end) { + if (prev == null || prev.start != opened.peek().start) + closeString(result) + prev = opened.pop() + } + return result + } + + private fun getSortedCodeMetaInfos( + metaInfos: Collection, + ): MutableList { + val result = metaInfos.toMutableList() + result.sortWith(Comparator { d1: ICodeMetaInfo, d2: ICodeMetaInfo -> + if (d1.start != d2.start) d1.start - d2.start else d2.end - d1.end + }) + return result + } + + private fun closeString(result: StringBuffer) = result.append("") + private fun openStartTag(result: StringBuffer) = result.append("") + private fun closeStartAndOpenNewTag(result: StringBuffer) = result.append("!> +) : ICodeMetaInfo { + + override val start: Int + get() = lineMarker.startOffset + override val end: Int + get() = lineMarker.endOffset + + val withDescription = true + + override fun asString(): String { + return renderConfiguration!!.asString(this) + } +} + +class HighlightingCodeMetaInfo( + override val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?, + val highlightingInfo: HighlightInfo +) : ICodeMetaInfo { + + override val start: Int + get() = highlightingInfo.startOffset + override val end: Int + get() = highlightingInfo.endOffset + + override fun asString(): String { + return renderConfiguration!!.asString(this) + } +} + +object CodeMetaInfoFactory { + private fun createCodeMetaInfo(obj: Any, renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?): Collection { + return when (obj) { + is Diagnostic -> { + if (renderConfiguration != null && renderConfiguration !is DiagnosticCodeMetaInfoRenderConfiguration) + throw IllegalArgumentException("Unexpected render configuration for CodeMetaInfo object $obj") + obj.textRanges.map { DiagnosticCodeMetaInfo(it.start, it.end, renderConfiguration, obj) } + } + is ActualDiagnostic -> { + if (renderConfiguration != null && renderConfiguration !is DiagnosticCodeMetaInfoRenderConfiguration) + throw IllegalArgumentException("Unexpected render configuration for CodeMetaInfo object $obj") + obj.diagnostic.textRanges.map { DiagnosticCodeMetaInfo(it.start, it.end, renderConfiguration, obj.diagnostic) } + } + is HighlightInfo -> { + if (renderConfiguration != null && renderConfiguration !is HighlightingRenderConfiguration) + throw IllegalArgumentException("Unexpected render configuration for CodeMetaInfo object $obj") + listOf(HighlightingCodeMetaInfo(renderConfiguration, obj)) + } + is LineMarkerInfo<*> -> { + if (renderConfiguration != null && renderConfiguration !is LineMarkerRenderConfiguration) + throw IllegalArgumentException("Unexpected render configuration for CodeMetaInfo object $obj") + listOf(LineMarkerCodeMetaInfo(renderConfiguration, obj)) + } + else -> throw IllegalArgumentException("Unknown type for creating CodeMetaInfo object $obj") + } + } + + fun getCodeMetaInfo( + objects: Collection, + renderConfiguration: AbstractCodeMetaInfoRenderConfiguration? + ): Collection { + return objects.flatMap { createCodeMetaInfo(it, renderConfiguration) } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeMetaInfo/renderConfigurations/AbstractCodeMetaInfoRenderConfiguration.kt b/idea/tests/org/jetbrains/kotlin/idea/codeMetaInfo/renderConfigurations/AbstractCodeMetaInfoRenderConfiguration.kt new file mode 100644 index 00000000000..f5a86b4caca --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/codeMetaInfo/renderConfigurations/AbstractCodeMetaInfoRenderConfiguration.kt @@ -0,0 +1,131 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations + +import com.intellij.codeInsight.daemon.impl.HighlightInfo +import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.openapi.util.text.StringUtil +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory1 +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.rendering.* +import org.jetbrains.kotlin.idea.codeMetaInfo.models.DiagnosticCodeMetaInfo +import org.jetbrains.kotlin.idea.codeMetaInfo.models.HighlightingCodeMetaInfo +import org.jetbrains.kotlin.idea.codeMetaInfo.models.ICodeMetaInfo +import org.jetbrains.kotlin.idea.codeMetaInfo.models.LineMarkerCodeMetaInfo + + +abstract class AbstractCodeMetaInfoRenderConfiguration(var renderParams: Boolean = true) { + + abstract fun asString(codeMetaInfo: ICodeMetaInfo): String + + open fun getAdditionalTags(codeMetaInfo: ICodeMetaInfo) = "" + + protected fun sanitizeLineMarkerTooltip(originalText: String?): String { + if (originalText == null) return "null" + val noHtmlTags = StringUtil.removeHtmlTags(originalText) + return sanitizeLineBreaks(noHtmlTags) + } + + protected fun sanitizeLineBreaks(originalText: String): String { + return StringUtil.replace(originalText, "\n", " ") + } +} + +open class DiagnosticCodeMetaInfoRenderConfiguration( + val withNewInference: Boolean = true, + val renderSeverity: Boolean = false +) : AbstractCodeMetaInfoRenderConfiguration() { + + override fun asString(codeMetaInfo: ICodeMetaInfo): String { + if (codeMetaInfo !is DiagnosticCodeMetaInfo) return "" + return (getTag(codeMetaInfo) + if (renderParams) "(\"${getParamsString(codeMetaInfo)}\")" else "") + .replace(Regex("""\r?\n"""), "") + } + + private fun getParamsString(codeMetaInfo: DiagnosticCodeMetaInfo): String { + val params = mutableListOf() + val renderer = when (codeMetaInfo.diagnostic.factory) { + is DebugInfoDiagnosticFactory1 -> DiagnosticWithParameters1Renderer( + "{0}", + Renderers.TO_STRING + ) as DiagnosticRenderer + else -> DefaultErrorMessages.getRendererForDiagnostic(codeMetaInfo.diagnostic) + } + if (renderer is AbstractDiagnosticWithParametersRenderer) { + val renderParameters = renderer.renderParameters(codeMetaInfo.diagnostic) + params.addAll(ContainerUtil.map(renderParameters) { it.toString() }) + } + if (renderSeverity) + params.add("severity='${codeMetaInfo.diagnostic.severity}'") + + params.add(getAdditionalTags(codeMetaInfo)) + + return params.filter { it.isNotEmpty() }.joinToString("; ") + } + + private fun getTag(codeMetaInfo: DiagnosticCodeMetaInfo): String { + return codeMetaInfo.diagnostic.factory.name + } +} + +open class LineMarkerRenderConfiguration(val renderDescription: Boolean = true) : AbstractCodeMetaInfoRenderConfiguration() { + override fun asString(codeMetaInfo: ICodeMetaInfo): String { + if (codeMetaInfo !is LineMarkerCodeMetaInfo) return "" + return getTag() + if (renderParams) "(\"${getParamsString(codeMetaInfo)}\")" else "" + } + + private fun getTag(): String { + return "LINE_MARKER" + } + + private fun getParamsString(lineMarkerCodeMetaInfo: LineMarkerCodeMetaInfo): String { + val params = mutableListOf() + + if (renderDescription) + params.add("descr='${sanitizeLineMarkerTooltip(lineMarkerCodeMetaInfo.lineMarker.lineMarkerTooltip)}'") + + params.add(getAdditionalTags(lineMarkerCodeMetaInfo)) + + return params.filter { it.isNotEmpty() }.joinToString("; ") + } +} + +open class HighlightingRenderConfiguration( + val renderDescription: Boolean = true, + val renderTextAttributesKey: Boolean = true, + val renderSeverity: Boolean = true +) : AbstractCodeMetaInfoRenderConfiguration() { + + override fun asString(codeMetaInfo: ICodeMetaInfo): String { + if (codeMetaInfo !is HighlightingCodeMetaInfo) return "" + return getTag() + if (renderParams) "(${getParamsString(codeMetaInfo)})" else "" + } + + private fun getTag(): String { + return "HIGHLIGHTING" + } + + private fun getParamsString(highlightingCodeMetaInfo: HighlightingCodeMetaInfo): String { + val params = mutableListOf() + + if (renderSeverity) + params.add("severity='${getSeverity(highlightingCodeMetaInfo.highlightingInfo)}'") + if (renderDescription) + params.add("descr='${sanitizeLineBreaks(highlightingCodeMetaInfo.highlightingInfo.description)}'") + if (renderTextAttributesKey) + params.add("textAttributesKey='${highlightingCodeMetaInfo.highlightingInfo.forcedTextAttributesKey}'") + + params.add(getAdditionalTags(highlightingCodeMetaInfo)) + + return params.filter { it.isNotEmpty() }.joinToString("; ") + } + + private fun getSeverity(highlightingInfo: HighlightInfo): String { + return if (highlightingInfo.severity == HighlightSeverity.INFORMATION) "info" else highlightingInfo.severity.toString() + .toLowerCase() + } +} \ No newline at end of file