New highlighting/line markers/diagnostics test infrastructure
The current test framework has a number of shortcomings, such as different markups for different kinds, those formats can not be mixed in one test file, lacks composability, re-use of it is complicated This commit contains the first version of the new test runner to which tests will be migrated in the future.
This commit is contained in:
@@ -62,7 +62,7 @@ object CheckerTestUtil {
|
|||||||
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+)(?:\(((?:".*?")(?:,\s*".*?")*)\))?"""
|
||||||
|
|
||||||
internal 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)
|
||||||
|
|
||||||
fun getDiagnosticsIncludingSyntaxErrors(
|
fun getDiagnosticsIncludingSyntaxErrors(
|
||||||
|
|||||||
@@ -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<AbstractCodeMetaInfoRenderConfiguration>) : DaemonAnalyzerTestCase() {
|
||||||
|
|
||||||
|
fun getDiagnosticCodeMetaInfos(
|
||||||
|
configuration: DiagnosticCodeMetaInfoRenderConfiguration?,
|
||||||
|
parseDirective: Boolean = true
|
||||||
|
): Collection<ICodeMetaInfo> {
|
||||||
|
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<ICodeMetaInfo> {
|
||||||
|
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<ICodeMetaInfo> {
|
||||||
|
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<ICodeMetaInfo>()
|
||||||
|
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<DiagnosticCodeMetaInfo>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val textWithCodeMetaInfo = CodeMetaInfoRenderer.renderTagsToText(codeMetaInfoForCheck, myEditor.document.text)
|
||||||
|
KotlinTestUtils.assertEqualsToFile(
|
||||||
|
expectedFile,
|
||||||
|
textWithCodeMetaInfo.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkHighlightErrorItemsInDiagnostics(
|
||||||
|
diagnostics: Collection<DiagnosticCodeMetaInfo>
|
||||||
|
) {
|
||||||
|
val highlightItems: List<ICodeMetaInfo> =
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ICodeMetaInfo>,
|
||||||
|
originalText: String
|
||||||
|
): StringBuffer {
|
||||||
|
val result = StringBuffer()
|
||||||
|
if (codeMetaInfos.isEmpty()) {
|
||||||
|
result.append(originalText)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
val sortedMetaInfos = getSortedCodeMetaInfos(codeMetaInfos)
|
||||||
|
val opened = Stack<ICodeMetaInfo>()
|
||||||
|
|
||||||
|
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<ICodeMetaInfo>,
|
||||||
|
): 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 openStartTag(result: StringBuffer) = result.append("<!")
|
||||||
|
private fun closeStartTag(result: StringBuffer) = result.append("!>")
|
||||||
|
private fun closeStartAndOpenNewTag(result: StringBuffer) = result.append("!><!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearFileFromDiagnosticMarkup(file: File) {
|
||||||
|
val text = file.readText()
|
||||||
|
val cleanText = clearTextFromDiagnosticMarkup(text)
|
||||||
|
file.writeText(cleanText)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearTextFromDiagnosticMarkup(text: String): String = CheckerTestUtil.rangeStartOrEndPattern.matcher(text).replaceAll("")
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* 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.models
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.daemon.LineMarkerInfo
|
||||||
|
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||||
|
import org.jetbrains.kotlin.checkers.diagnostics.ActualDiagnostic
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
|
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.editor.fixers.end
|
||||||
|
import org.jetbrains.kotlin.idea.editor.fixers.start
|
||||||
|
|
||||||
|
interface ICodeMetaInfo {
|
||||||
|
|
||||||
|
val start: Int
|
||||||
|
val end: Int
|
||||||
|
val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?
|
||||||
|
|
||||||
|
fun asString(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
class DiagnosticCodeMetaInfo(
|
||||||
|
override val start: Int,
|
||||||
|
override val end: Int,
|
||||||
|
override val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?,
|
||||||
|
val diagnostic: Diagnostic
|
||||||
|
) :
|
||||||
|
ICodeMetaInfo {
|
||||||
|
|
||||||
|
override fun asString(): String {
|
||||||
|
return renderConfiguration!!.asString(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LineMarkerCodeMetaInfo(
|
||||||
|
override val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?,
|
||||||
|
val lineMarker: LineMarkerInfo<*>
|
||||||
|
) : 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<ICodeMetaInfo> {
|
||||||
|
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<Any>,
|
||||||
|
renderConfiguration: AbstractCodeMetaInfoRenderConfiguration?
|
||||||
|
): Collection<ICodeMetaInfo> {
|
||||||
|
return objects.flatMap { createCodeMetaInfo(it, renderConfiguration) }
|
||||||
|
}
|
||||||
|
}
|
||||||
+131
@@ -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<String>()
|
||||||
|
val renderer = when (codeMetaInfo.diagnostic.factory) {
|
||||||
|
is DebugInfoDiagnosticFactory1 -> DiagnosticWithParameters1Renderer(
|
||||||
|
"{0}",
|
||||||
|
Renderers.TO_STRING
|
||||||
|
) as DiagnosticRenderer<Diagnostic>
|
||||||
|
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<String>()
|
||||||
|
|
||||||
|
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<String>()
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user