Added support for various platforms for CodeMetaInfo tests. ^KT-41996

The tests need to be able to parse and render CodeMetaInfo for different
 platforms. For example, we must draw a run gutter a MAC application on
 OSX, but for a Win application, the gutter should not be drawn at the
 same time. This commit allows to do such checks. In order to indicate
 on which platform it should be rendered, it is necessary to specify the
 platform for a specific CodeMetaInfo in the test data in the format
 {OSX,Unix,Win}, if the block with platforms is missing, this means
 that it should be rendered on each platform.

<!LINE_MARKER{OSX}(...)!> - should only be on OSX
<!LINE_MARKER(...)!> - should be on any platform
This commit is contained in:
Alexander Dudinsky
2020-10-10 10:05:03 +03:00
parent 84d24e5b76
commit 94bf38a7b5
6 changed files with 226 additions and 140 deletions
@@ -60,7 +60,7 @@ object CheckerTestUtil {
const val OLD_INFERENCE_PREFIX = "OI" const val OLD_INFERENCE_PREFIX = "OI"
private const val IGNORE_DIAGNOSTIC_PARAMETER = "IGNORE" private const val IGNORE_DIAGNOSTIC_PARAMETER = "IGNORE"
private const val INDIVIDUAL_DIAGNOSTIC = """(\w+;)?(\w+:)?(\w+)(?:\(((?:".*?")(?:,\s*".*?")*)\))?""" private const val INDIVIDUAL_DIAGNOSTIC = """(\w+;)?(\w+:)?(\w+)(\{[\w;]+})?(?:\(((?:".*?")(?:,\s*".*?")*)\))?"""
val rangeStartOrEndPattern = Pattern.compile("(<!$INDIVIDUAL_DIAGNOSTIC(,\\s*$INDIVIDUAL_DIAGNOSTIC)*!>)|(<!>)") val rangeStartOrEndPattern = Pattern.compile("(<!$INDIVIDUAL_DIAGNOSTIC(,\\s*$INDIVIDUAL_DIAGNOSTIC)*!>)|(<!>)")
val individualDiagnosticPattern: Pattern = Pattern.compile(INDIVIDUAL_DIAGNOSTIC) val individualDiagnosticPattern: Pattern = Pattern.compile(INDIVIDUAL_DIAGNOSTIC)
@@ -32,13 +32,12 @@ import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest
import org.jetbrains.kotlin.checkers.diagnostics.SyntaxErrorDiagnostic import org.jetbrains.kotlin.checkers.diagnostics.SyntaxErrorDiagnostic
import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil
import org.jetbrains.kotlin.checkers.utils.DiagnosticsRenderingConfiguration import org.jetbrains.kotlin.checkers.utils.DiagnosticsRenderingConfiguration
import org.jetbrains.kotlin.daemon.common.OSKind
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.AbstractDiagnostic import org.jetbrains.kotlin.diagnostics.AbstractDiagnostic
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeMetaInfo.models.CodeMetaInfoFactory import org.jetbrains.kotlin.idea.codeMetaInfo.models.*
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.AbstractCodeMetaInfoRenderConfiguration
import org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations.DiagnosticCodeMetaInfoRenderConfiguration import org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations.DiagnosticCodeMetaInfoRenderConfiguration
import org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations.HighlightingRenderConfiguration import org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations.HighlightingRenderConfiguration
@@ -53,12 +52,15 @@ import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File import java.io.File
import java.nio.file.Paths import java.nio.file.Paths
class CodeMetaInfoTestCase(val codeMetaInfoTypes: Collection<AbstractCodeMetaInfoRenderConfiguration>) : DaemonAnalyzerTestCase() { class CodeMetaInfoTestCase(
val codeMetaInfoTypes: Collection<AbstractCodeMetaInfoRenderConfiguration>,
val checkNoDiagnosticError: Boolean = false
) : DaemonAnalyzerTestCase() {
fun getDiagnosticCodeMetaInfos( fun getDiagnosticCodeMetaInfos(
configuration: DiagnosticCodeMetaInfoRenderConfiguration?, configuration: DiagnosticCodeMetaInfoRenderConfiguration = DiagnosticCodeMetaInfoRenderConfiguration(),
parseDirective: Boolean = true parseDirective: Boolean = true
): Collection<ICodeMetaInfo> { ): List<CodeMetaInfo> {
val tempSourceKtFile = PsiManager.getInstance(project).findFile(file.virtualFile) as KtFile val tempSourceKtFile = PsiManager.getInstance(project).findFile(file.virtualFile) as KtFile
val resolutionFacade = tempSourceKtFile.getResolutionFacade() val resolutionFacade = tempSourceKtFile.getResolutionFacade()
val (bindingContext, moduleDescriptor) = resolutionFacade.analyzeWithAllCompilerChecks(listOf(tempSourceKtFile)) val (bindingContext, moduleDescriptor) = resolutionFacade.analyzeWithAllCompilerChecks(listOf(tempSourceKtFile))
@@ -77,23 +79,23 @@ class CodeMetaInfoTestCase(val codeMetaInfoTypes: Collection<AbstractCodeMetaInf
dataFlowValueFactory = resolutionFacade.getDataFlowValueFactory(), dataFlowValueFactory = resolutionFacade.getDataFlowValueFactory(),
moduleDescriptor = moduleDescriptor as ModuleDescriptorImpl moduleDescriptor = moduleDescriptor as ModuleDescriptorImpl
).map { it.diagnostic }.filter { !parseDirective || diagnosticsFilter.value(it) } ).map { it.diagnostic }.filter { !parseDirective || diagnosticsFilter.value(it) }
configuration?.renderParams = directives.contains(BaseDiagnosticsTest.RENDER_DIAGNOSTICS_MESSAGES) configuration.renderParams = directives.contains(BaseDiagnosticsTest.RENDER_DIAGNOSTICS_MESSAGES)
return CodeMetaInfoFactory.getCodeMetaInfo(diagnostics, configuration) return getCodeMetaInfo(diagnostics, configuration)
} }
fun getLineMarkerCodeMetaInfos(configuration: LineMarkerRenderConfiguration): Collection<ICodeMetaInfo> { fun getLineMarkerCodeMetaInfos(configuration: LineMarkerRenderConfiguration): Collection<CodeMetaInfo> {
if ("!CHECK_HIGHLIGHTING" in file.text) if ("!CHECK_HIGHLIGHTING" in file.text)
return emptyList() return emptyList()
CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, TIntArrayList().toNativeArray(), false) CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, TIntArrayList().toNativeArray(), false)
val lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(getDocument(file), project) val lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(getDocument(file), project)
return CodeMetaInfoFactory.getCodeMetaInfo(lineMarkers, configuration) return getCodeMetaInfo(lineMarkers, configuration)
} }
fun getHighlightingCodeMetaInfos(configuration: HighlightingRenderConfiguration?): Collection<ICodeMetaInfo> { fun getHighlightingCodeMetaInfos(configuration: HighlightingRenderConfiguration): Collection<CodeMetaInfo> {
val infos = CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, TIntArrayList().toNativeArray(), false) val infos = CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, TIntArrayList().toNativeArray(), false)
return CodeMetaInfoFactory.getCodeMetaInfo(infos, configuration) return getCodeMetaInfo(infos, configuration)
} }
fun checkFile(expectedFile: File, project: Project, editor: Editor) { fun checkFile(expectedFile: File, project: Project, editor: Editor) {
@@ -113,8 +115,7 @@ class CodeMetaInfoTestCase(val codeMetaInfoTypes: Collection<AbstractCodeMetaInf
} }
fun check(expectedFile: File) { fun check(expectedFile: File) {
val codeMetaInfoForCheck = mutableListOf<CodeMetaInfo>()
val codeMetaInfoForCheck = mutableListOf<ICodeMetaInfo>()
PsiDocumentManager.getInstance(myProject).commitAllDocuments() PsiDocumentManager.getInstance(myProject).commitAllDocuments()
//to load text //to load text
@@ -144,22 +145,46 @@ class CodeMetaInfoTestCase(val codeMetaInfoTypes: Collection<AbstractCodeMetaInf
!codeMetaInfoTypes.any { it is HighlightingRenderConfiguration } !codeMetaInfoTypes.any { it is HighlightingRenderConfiguration }
) { ) {
checkHighlightErrorItemsInDiagnostics( checkHighlightErrorItemsInDiagnostics(
getDiagnosticCodeMetaInfos(null, false).filterIsInstance<DiagnosticCodeMetaInfo>() getDiagnosticCodeMetaInfos(DiagnosticCodeMetaInfoRenderConfiguration(), false).filterIsInstance<DiagnosticCodeMetaInfo>()
) )
} }
val parsedMetaInfo = CodeMetaInfoParser.getCodeMetaInfoFromText(expectedFile.readText()).toMutableList()
codeMetaInfoForCheck.forEach { codeMetaInfo ->
val correspondingParsed = parsedMetaInfo.firstOrNull { it == codeMetaInfo }
if (correspondingParsed != null) {
parsedMetaInfo.remove(correspondingParsed)
codeMetaInfo.platforms.addAll(correspondingParsed.platforms)
if (correspondingParsed.platforms.isNotEmpty() && OSKind.current.toString() !in correspondingParsed.platforms)
codeMetaInfo.platforms.add(OSKind.current.toString())
}
}
if (parsedMetaInfo.isNotEmpty())
parsedMetaInfo.forEach {
if (it.platforms.isNotEmpty() && OSKind.current.toString() !in it.platforms) codeMetaInfoForCheck.add(
it
)
}
val textWithCodeMetaInfo = CodeMetaInfoRenderer.renderTagsToText(codeMetaInfoForCheck, myEditor.document.text) val textWithCodeMetaInfo = CodeMetaInfoRenderer.renderTagsToText(codeMetaInfoForCheck, myEditor.document.text)
KotlinTestUtils.assertEqualsToFile( KotlinTestUtils.assertEqualsToFile(
expectedFile, expectedFile,
textWithCodeMetaInfo.toString() textWithCodeMetaInfo.toString()
) )
if (checkNoDiagnosticError) {
val diagnosticsErrors =
getDiagnosticCodeMetaInfos().filter { (it as DiagnosticCodeMetaInfo).diagnostic.severity == Severity.ERROR }
assertTrue(
"Diagnostics with severity ERROR were found: ${diagnosticsErrors.joinToString { it.asString() }}",
diagnosticsErrors.isEmpty()
)
}
} }
private fun checkHighlightErrorItemsInDiagnostics( private fun checkHighlightErrorItemsInDiagnostics(
diagnostics: Collection<DiagnosticCodeMetaInfo> diagnostics: Collection<DiagnosticCodeMetaInfo>
) { ) {
val highlightItems: List<ICodeMetaInfo> = val highlightItems: List<CodeMetaInfo> =
getHighlightingCodeMetaInfos(null).filter { (it as HighlightingCodeMetaInfo).highlightingInfo.severity == HighlightSeverity.ERROR } getHighlightingCodeMetaInfos(HighlightingRenderConfiguration()).filter { (it as HighlightingCodeMetaInfo).highlightingInfo.severity == HighlightSeverity.ERROR }
highlightItems.forEach { highlightingCodeMetaInfo -> highlightItems.forEach { highlightingCodeMetaInfo ->
assert( assert(
@@ -201,7 +226,7 @@ abstract class AbstractHighlightingCodeMetaInfoTest : AbstractCodeMetaInfoTest()
} }
abstract class AbstractCodeMetaInfoTest : AbstractMultiModuleTest() { abstract class AbstractCodeMetaInfoTest : AbstractMultiModuleTest() {
open val checkNoDiagnosticError get() = false
open fun getConfigurations() = listOf( open fun getConfigurations() = listOf(
DiagnosticCodeMetaInfoRenderConfiguration(), DiagnosticCodeMetaInfoRenderConfiguration(),
LineMarkerRenderConfiguration(), LineMarkerRenderConfiguration(),
@@ -218,7 +243,7 @@ abstract class AbstractCodeMetaInfoTest : AbstractMultiModuleTest() {
fun doTest(testDataPath: String) { fun doTest(testDataPath: String) {
val testRoot = File(testDataPath) val testRoot = File(testDataPath)
val checker = CodeMetaInfoTestCase(getConfigurations()) val checker = CodeMetaInfoTestCase(getConfigurations(), checkNoDiagnosticError)
setupProject(testDataPath) setupProject(testDataPath)
for (module in ModuleManager.getInstance(project).modules) { for (module in ModuleManager.getInstance(project).modules) {
@@ -237,16 +262,13 @@ abstract class AbstractCodeMetaInfoTest : AbstractMultiModuleTest() {
val tempRootPath = Paths.get(containingRoot.path) val tempRootPath = Paths.get(containingRoot.path)
val tempProjectDirPath = tempRootPath.parent val tempProjectDirPath = tempRootPath.parent
val tempSourcePath = Paths.get(path) val tempSourcePath = Paths.get(path)
val relativeToProjectRootPath = tempProjectDirPath.relativize(tempSourcePath) val relativeToProjectRootPath = tempProjectDirPath.relativize(tempSourcePath)
val testSourcesProjectDirPath = testDir.toPath() val testSourcesProjectDirPath = testDir.toPath()
val testSourcePath = testSourcesProjectDirPath.resolve(relativeToProjectRootPath) val testSourcePath = testSourcesProjectDirPath.resolve(relativeToProjectRootPath)
require(testSourcePath.exists()) { require(testSourcePath.exists()) {
"Can't find file in testdata for copied file $this: checked at path ${testSourcePath.toAbsolutePath()}" "Can't find file in testdata for copied file $this: checked at path ${testSourcePath.toAbsolutePath()}"
} }
return testSourcePath.toFile() return testSourcePath.toFile()
} }
} }
@@ -0,0 +1,65 @@
/*
* 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.idea.codeMetaInfo.models.ParsedCodeMetaInfo
import org.junit.Assert
object CodeMetaInfoParser {
private val openingRegex = "(<!([^>]+?)!>)".toRegex()
private val closingRegex = "(<!>)".toRegex()
private val descriptionRegex = "\\(\".*?\"\\)".toRegex()
private val platformRegex = "\\{(.+)}".toRegex()
fun getCodeMetaInfoFromText(renderedText: String): List<ParsedCodeMetaInfo> {
var text = renderedText
val openingMatchResults = Stack<MatchResult>()
val closingMatchResults = Stack<MatchResult>()
val result = mutableListOf<ParsedCodeMetaInfo>()
while (true) {
var openingStartOffset = Int.MAX_VALUE
var closingStartOffset = Int.MAX_VALUE
val opening = openingRegex.find(text)
val closing = closingRegex.find(text)
if (opening == null && closing == null) break
if (opening != null)
openingStartOffset = opening.range.first
if (closing != null)
closingStartOffset = closing.range.first
text = if (openingStartOffset < closingStartOffset) {
openingMatchResults.push(opening)
text.removeRange(openingStartOffset, opening!!.range.last + 1)
} else {
closingMatchResults.push(closing)
text.removeRange(closingStartOffset, closing!!.range.last + 1)
}
}
if (openingMatchResults.size != closingMatchResults.size) {
Assert.fail("Opening and closing tags counts are not equals")
}
while (!openingMatchResults.isEmpty()) {
val openingMatchResult = openingMatchResults.pop()
val closingMatchResult = closingMatchResults.pop()
val metaInfoWithoutParams = openingMatchResult.groups[2]!!.value.replace(descriptionRegex, "")
metaInfoWithoutParams.split(",").forEach {
val tag = platformRegex.replace(it, "").trim()
val platforms =
if (platformRegex.containsMatchIn(it)) platformRegex.find(it)!!.destructured.component1().split(";") else listOf()
result.add(
ParsedCodeMetaInfo(
openingMatchResult.range.first, closingMatchResult.range.first, platforms.toMutableList(), tag,
)
)
}
}
return result
}
}
@@ -7,13 +7,12 @@ package org.jetbrains.kotlin.idea.codeMetaInfo
import com.intellij.util.containers.Stack import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil
import org.jetbrains.kotlin.idea.codeMetaInfo.models.ICodeMetaInfo import org.jetbrains.kotlin.idea.codeMetaInfo.models.CodeMetaInfo
import java.io.File import java.io.File
object CodeMetaInfoRenderer { object CodeMetaInfoRenderer {
fun renderTagsToText( fun renderTagsToText(
codeMetaInfos: List<ICodeMetaInfo>, codeMetaInfos: List<CodeMetaInfo>,
originalText: String originalText: String
): StringBuffer { ): StringBuffer {
val result = StringBuffer() val result = StringBuffer()
@@ -22,25 +21,18 @@ object CodeMetaInfoRenderer {
return result return result
} }
val sortedMetaInfos = getSortedCodeMetaInfos(codeMetaInfos) val sortedMetaInfos = getSortedCodeMetaInfos(codeMetaInfos)
val opened = Stack<ICodeMetaInfo>() val opened = Stack<CodeMetaInfo>()
for (i in originalText.indices) { for ((i, c) in originalText.withIndex()) {
val c = originalText[i] checkOpenedAndCloseStringIfNeeded(opened, i, result)
var prev: ICodeMetaInfo? = null val matchedCodeMetaInfos = sortedMetaInfos.filter { it.start == i }
if (matchedCodeMetaInfos.isNotEmpty()) {
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) openStartTag(result)
val matchedCodeMetaInfos = sortedMetaInfos.filter { it.start == i }.toMutableList()
val iterator = matchedCodeMetaInfos.listIterator() val iterator = matchedCodeMetaInfos.listIterator()
var current: ICodeMetaInfo? = iterator.next() var current: CodeMetaInfo? = iterator.next()
while (current != null) { while (current != null) {
val next: ICodeMetaInfo? = if (iterator.hasNext()) iterator.next() else null val next: CodeMetaInfo? = if (iterator.hasNext()) iterator.next() else null
opened.push(current) opened.push(current)
result.append(current.asString()) result.append(current.asString())
when { when {
@@ -56,30 +48,27 @@ object CodeMetaInfoRenderer {
} }
result.append(c) result.append(c)
} }
var prev: ICodeMetaInfo? = null checkOpenedAndCloseStringIfNeeded(opened, originalText.length, result)
while (!opened.isEmpty() && originalText.length == opened.peek().end) {
if (prev == null || prev.start != opened.peek().start)
closeString(result)
prev = opened.pop()
}
return result return result
} }
private fun getSortedCodeMetaInfos( private fun getSortedCodeMetaInfos(metaInfos: Collection<CodeMetaInfo>): List<CodeMetaInfo> {
metaInfos: Collection<ICodeMetaInfo>, return metaInfos.sortedWith(compareBy<CodeMetaInfo> { it.start }.then(compareByDescending { it.end }))
): MutableList<ICodeMetaInfo> {
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 closeString(result: StringBuffer) = result.append("<!>")
private fun openStartTag(result: StringBuffer) = result.append("<!") private fun openStartTag(result: StringBuffer) = result.append("<!")
private fun closeStartTag(result: StringBuffer) = result.append("!>") private fun closeStartTag(result: StringBuffer) = result.append("!>")
private fun closeStartAndOpenNewTag(result: StringBuffer) = result.append("!><!") private fun closeStartAndOpenNewTag(result: StringBuffer) = result.append("!><!")
private fun checkOpenedAndCloseStringIfNeeded(opened: Stack<CodeMetaInfo>, end: Int, result: StringBuffer) {
var prev: CodeMetaInfo? = null
while (!opened.isEmpty() && end == opened.peek().end) {
if (prev == null || prev.start != opened.peek().start)
closeString(result)
prev = opened.pop()
}
}
} }
fun clearFileFromDiagnosticMarkup(file: File) { fun clearFileFromDiagnosticMarkup(file: File) {
@@ -16,91 +16,103 @@ import org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations.LineMarkerRen
import org.jetbrains.kotlin.idea.editor.fixers.end import org.jetbrains.kotlin.idea.editor.fixers.end
import org.jetbrains.kotlin.idea.editor.fixers.start import org.jetbrains.kotlin.idea.editor.fixers.start
interface ICodeMetaInfo { interface CodeMetaInfo {
val start: Int val start: Int
val end: Int val end: Int
val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration? val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration
val platforms: MutableList<String>
fun asString(): String fun asString(): String
fun getTag(): String
} }
class DiagnosticCodeMetaInfo( class DiagnosticCodeMetaInfo(
override val start: Int, override val start: Int,
override val end: Int, override val end: Int,
override val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?, override val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration,
val diagnostic: Diagnostic val diagnostic: Diagnostic
) : ) : CodeMetaInfo {
ICodeMetaInfo { override val platforms: MutableList<String> = mutableListOf()
override fun asString(): String { override fun asString() = renderConfiguration.asString(this)
return renderConfiguration!!.asString(this)
} override fun getTag() = (renderConfiguration as DiagnosticCodeMetaInfoRenderConfiguration).getTag(this)
} }
class LineMarkerCodeMetaInfo( class LineMarkerCodeMetaInfo(
override val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?, override val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration,
val lineMarker: LineMarkerInfo<*> val lineMarker: LineMarkerInfo<*>
) : ICodeMetaInfo { ) : CodeMetaInfo {
override val start: Int override val start: Int
get() = lineMarker.startOffset get() = lineMarker.startOffset
override val end: Int override val end: Int
get() = lineMarker.endOffset get() = lineMarker.endOffset
override val platforms: MutableList<String> = mutableListOf()
val withDescription = true override fun asString() = renderConfiguration.asString(this)
override fun asString(): String { override fun getTag() = (renderConfiguration as LineMarkerRenderConfiguration).getTag()
return renderConfiguration!!.asString(this)
}
} }
class HighlightingCodeMetaInfo( class HighlightingCodeMetaInfo(
override val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?, override val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration,
val highlightingInfo: HighlightInfo val highlightingInfo: HighlightInfo
) : ICodeMetaInfo { ) : CodeMetaInfo {
override val start: Int override val start: Int
get() = highlightingInfo.startOffset get() = highlightingInfo.startOffset
override val end: Int override val end: Int
get() = highlightingInfo.endOffset get() = highlightingInfo.endOffset
override val platforms: MutableList<String> = mutableListOf()
override fun asString(): String { override fun asString() = renderConfiguration.asString(this)
return renderConfiguration!!.asString(this)
override fun getTag() = (renderConfiguration as HighlightingRenderConfiguration).getTag()
}
class ParsedCodeMetaInfo(
override val start: Int,
override val end: Int,
override val platforms: MutableList<String>,
private val tag: String
) : CodeMetaInfo {
override val renderConfiguration = object : AbstractCodeMetaInfoRenderConfiguration(false) {}
override fun asString() = renderConfiguration.asString(this)
override fun equals(other: Any?): Boolean {
if (other == null || other !is CodeMetaInfo) return false
return this.tag == other.getTag() && this.start == other.start && this.end == other.end
}
override fun getTag() = tag
}
fun createCodeMetaInfo(obj: Any, renderConfiguration: AbstractCodeMetaInfoRenderConfiguration): List<CodeMetaInfo> {
fun errorMessage() = "Unexpected render configuration for object $obj"
return when (obj) {
is Diagnostic -> {
require(renderConfiguration is DiagnosticCodeMetaInfoRenderConfiguration, ::errorMessage)
obj.textRanges.map { DiagnosticCodeMetaInfo(it.start, it.end, renderConfiguration, obj) }
}
is ActualDiagnostic -> {
require(renderConfiguration is DiagnosticCodeMetaInfoRenderConfiguration, ::errorMessage)
obj.diagnostic.textRanges.map { DiagnosticCodeMetaInfo(it.start, it.end, renderConfiguration, obj.diagnostic) }
}
is HighlightInfo -> {
require(renderConfiguration is HighlightingRenderConfiguration, ::errorMessage)
listOf(HighlightingCodeMetaInfo(renderConfiguration, obj))
}
is LineMarkerInfo<*> -> {
require(renderConfiguration is LineMarkerRenderConfiguration, ::errorMessage)
listOf(LineMarkerCodeMetaInfo(renderConfiguration, obj))
}
else -> throw IllegalArgumentException("Unknown type for creating CodeMetaInfo object $obj")
} }
} }
object CodeMetaInfoFactory { fun getCodeMetaInfo(
private fun createCodeMetaInfo(obj: Any, renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?): Collection<ICodeMetaInfo> { objects: List<Any>,
return when (obj) { renderConfiguration: AbstractCodeMetaInfoRenderConfiguration
is Diagnostic -> { ): List<CodeMetaInfo> {
if (renderConfiguration != null && renderConfiguration !is DiagnosticCodeMetaInfoRenderConfiguration) return objects.flatMap { createCodeMetaInfo(it, renderConfiguration) }
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<Any>,
renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?
): Collection<ICodeMetaInfo> {
return objects.flatMap { createCodeMetaInfo(it, renderConfiguration) }
}
}
@@ -5,8 +5,6 @@
package org.jetbrains.kotlin.idea.codeMetaInfo.renderConfigurations 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.openapi.util.text.StringUtil
import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory1 import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory1
@@ -14,39 +12,48 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.rendering.* import org.jetbrains.kotlin.diagnostics.rendering.*
import org.jetbrains.kotlin.idea.codeMetaInfo.models.DiagnosticCodeMetaInfo import org.jetbrains.kotlin.idea.codeMetaInfo.models.DiagnosticCodeMetaInfo
import org.jetbrains.kotlin.idea.codeMetaInfo.models.HighlightingCodeMetaInfo import org.jetbrains.kotlin.idea.codeMetaInfo.models.HighlightingCodeMetaInfo
import org.jetbrains.kotlin.idea.codeMetaInfo.models.ICodeMetaInfo import org.jetbrains.kotlin.idea.codeMetaInfo.models.CodeMetaInfo
import org.jetbrains.kotlin.idea.codeMetaInfo.models.LineMarkerCodeMetaInfo import org.jetbrains.kotlin.idea.codeMetaInfo.models.LineMarkerCodeMetaInfo
abstract class AbstractCodeMetaInfoRenderConfiguration(var renderParams: Boolean = true) { abstract class AbstractCodeMetaInfoRenderConfiguration(var renderParams: Boolean = true) {
abstract fun asString(codeMetaInfo: ICodeMetaInfo): String open fun asString(codeMetaInfo: CodeMetaInfo) = codeMetaInfo.getTag() + getPlatformsString(codeMetaInfo)
open fun getAdditionalTags(codeMetaInfo: ICodeMetaInfo) = "" open fun getAdditionalParams(codeMetaInfo: CodeMetaInfo) = ""
protected fun sanitizeLineMarkerTooltip(originalText: String?): String { protected fun sanitizeLineMarkerTooltip(originalText: String?): String {
if (originalText == null) return "null" if (originalText == null) return "null"
val noHtmlTags = StringUtil.removeHtmlTags(originalText) val noHtmlTags = StringUtil.removeHtmlTags(originalText).replace(" ", "")
return sanitizeLineBreaks(noHtmlTags) return sanitizeLineBreaks(noHtmlTags)
} }
protected fun sanitizeLineBreaks(originalText: String): String { protected fun sanitizeLineBreaks(originalText: String): String {
return StringUtil.replace(originalText, "\n", " ") return StringUtil.replace(originalText, "\n", " ")
} }
protected fun getPlatformsString(codeMetaInfo: CodeMetaInfo): String {
if (codeMetaInfo.platforms.isEmpty()) return ""
return "{${codeMetaInfo.platforms.joinToString(";")}}"
}
} }
open class DiagnosticCodeMetaInfoRenderConfiguration( open class DiagnosticCodeMetaInfoRenderConfiguration(
val withNewInference: Boolean = true, val withNewInference: Boolean = true,
val renderSeverity: Boolean = false val renderSeverity: Boolean = false
) : AbstractCodeMetaInfoRenderConfiguration() { ) : AbstractCodeMetaInfoRenderConfiguration() {
private val crossPlatformLineBreak = """\r?\n""".toRegex()
override fun asString(codeMetaInfo: ICodeMetaInfo): String { override fun asString(codeMetaInfo: CodeMetaInfo): String {
if (codeMetaInfo !is DiagnosticCodeMetaInfo) return "" if (codeMetaInfo !is DiagnosticCodeMetaInfo) return ""
return (getTag(codeMetaInfo) + if (renderParams) "(\"${getParamsString(codeMetaInfo)}\")" else "") return (getTag(codeMetaInfo)
.replace(Regex("""\r?\n"""), "") + getPlatformsString(codeMetaInfo)
+ getParamsString(codeMetaInfo))
.replace(crossPlatformLineBreak, "")
} }
private fun getParamsString(codeMetaInfo: DiagnosticCodeMetaInfo): String { private fun getParamsString(codeMetaInfo: DiagnosticCodeMetaInfo): String {
if (!renderParams) return ""
val params = mutableListOf<String>() val params = mutableListOf<String>()
val renderer = when (codeMetaInfo.diagnostic.factory) { val renderer = when (codeMetaInfo.diagnostic.factory) {
is DebugInfoDiagnosticFactory1 -> DiagnosticWithParameters1Renderer( is DebugInfoDiagnosticFactory1 -> DiagnosticWithParameters1Renderer(
@@ -62,25 +69,23 @@ open class DiagnosticCodeMetaInfoRenderConfiguration(
if (renderSeverity) if (renderSeverity)
params.add("severity='${codeMetaInfo.diagnostic.severity}'") params.add("severity='${codeMetaInfo.diagnostic.severity}'")
params.add(getAdditionalTags(codeMetaInfo)) params.add(getAdditionalParams(codeMetaInfo))
return params.filter { it.isNotEmpty() }.joinToString("; ") return "(\"${params.filter { it.isNotEmpty() }.joinToString("; ")}\")"
} }
private fun getTag(codeMetaInfo: DiagnosticCodeMetaInfo): String { fun getTag(codeMetaInfo: DiagnosticCodeMetaInfo): String {
return codeMetaInfo.diagnostic.factory.name return codeMetaInfo.diagnostic.factory.name
} }
} }
open class LineMarkerRenderConfiguration(val renderDescription: Boolean = true) : AbstractCodeMetaInfoRenderConfiguration() { open class LineMarkerRenderConfiguration(val renderDescription: Boolean = true) : AbstractCodeMetaInfoRenderConfiguration() {
override fun asString(codeMetaInfo: ICodeMetaInfo): String { override fun asString(codeMetaInfo: CodeMetaInfo): String {
if (codeMetaInfo !is LineMarkerCodeMetaInfo) return "" if (codeMetaInfo !is LineMarkerCodeMetaInfo) return ""
return getTag() + if (renderParams) "(\"${getParamsString(codeMetaInfo)}\")" else "" return getTag() + if (renderParams) "(\"${getParamsString(codeMetaInfo)}\")" else ""
} }
private fun getTag(): String { fun getTag() = "LINE_MARKER"
return "LINE_MARKER"
}
private fun getParamsString(lineMarkerCodeMetaInfo: LineMarkerCodeMetaInfo): String { private fun getParamsString(lineMarkerCodeMetaInfo: LineMarkerCodeMetaInfo): String {
val params = mutableListOf<String>() val params = mutableListOf<String>()
@@ -88,7 +93,7 @@ open class LineMarkerRenderConfiguration(val renderDescription: Boolean = true)
if (renderDescription) if (renderDescription)
params.add("descr='${sanitizeLineMarkerTooltip(lineMarkerCodeMetaInfo.lineMarker.lineMarkerTooltip)}'") params.add("descr='${sanitizeLineMarkerTooltip(lineMarkerCodeMetaInfo.lineMarker.lineMarkerTooltip)}'")
params.add(getAdditionalTags(lineMarkerCodeMetaInfo)) params.add(getAdditionalParams(lineMarkerCodeMetaInfo))
return params.filter { it.isNotEmpty() }.joinToString("; ") return params.filter { it.isNotEmpty() }.joinToString("; ")
} }
@@ -100,32 +105,25 @@ open class HighlightingRenderConfiguration(
val renderSeverity: Boolean = true val renderSeverity: Boolean = true
) : AbstractCodeMetaInfoRenderConfiguration() { ) : AbstractCodeMetaInfoRenderConfiguration() {
override fun asString(codeMetaInfo: ICodeMetaInfo): String { override fun asString(codeMetaInfo: CodeMetaInfo): String {
if (codeMetaInfo !is HighlightingCodeMetaInfo) return "" if (codeMetaInfo !is HighlightingCodeMetaInfo) return ""
return getTag() + if (renderParams) "(${getParamsString(codeMetaInfo)})" else "" return getTag() + if (renderParams) "(${getParamsString(codeMetaInfo)})" else ""
} }
private fun getTag(): String { fun getTag() = "HIGHLIGHTING"
return "HIGHLIGHTING"
}
private fun getParamsString(highlightingCodeMetaInfo: HighlightingCodeMetaInfo): String { private fun getParamsString(highlightingCodeMetaInfo: HighlightingCodeMetaInfo): String {
val params = mutableListOf<String>() val params = mutableListOf<String>()
if (renderSeverity) if (renderSeverity)
params.add("severity='${getSeverity(highlightingCodeMetaInfo.highlightingInfo)}'") params.add("severity='${highlightingCodeMetaInfo.highlightingInfo.severity}'")
if (renderDescription) if (renderDescription)
params.add("descr='${sanitizeLineBreaks(highlightingCodeMetaInfo.highlightingInfo.description)}'") params.add("descr='${sanitizeLineBreaks(highlightingCodeMetaInfo.highlightingInfo.description)}'")
if (renderTextAttributesKey) if (renderTextAttributesKey)
params.add("textAttributesKey='${highlightingCodeMetaInfo.highlightingInfo.forcedTextAttributesKey}'") params.add("textAttributesKey='${highlightingCodeMetaInfo.highlightingInfo.forcedTextAttributesKey}'")
params.add(getAdditionalTags(highlightingCodeMetaInfo)) params.add(getAdditionalParams(highlightingCodeMetaInfo))
return params.filter { it.isNotEmpty() }.joinToString("; ") return params.filter { it.isNotEmpty() }.joinToString("; ")
} }
private fun getSeverity(highlightingInfo: HighlightInfo): String {
return if (highlightingInfo.severity == HighlightSeverity.INFORMATION) "info" else highlightingInfo.severity.toString()
.toLowerCase()
}
} }