Prepare CLI reporting infrastructure for non-PSI diagnostics

This commit is contained in:
Ilya Chernikov
2021-10-04 12:22:03 +02:00
parent 5446168770
commit 1ce4075112
10 changed files with 182 additions and 28 deletions
@@ -12,10 +12,12 @@ import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageUtil
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.diagnostics.impl.BaseDiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDefaultErrorMessages
import java.io.Closeable
import java.io.File
import java.io.InputStreamReader
object FirDiagnosticsCompilerResultsReporter {
fun reportDiagnostics(diagnostics: Collection<KtDiagnostic>, reporter: MessageCollector): Boolean {
@@ -27,6 +29,38 @@ object FirDiagnosticsCompilerResultsReporter {
return hasErrors
}
fun reportToCollector(diagnosticsReporter: BaseDiagnosticReporter, messageCollector: MessageCollector): Boolean {
var hasErrors = false
for (filePath in diagnosticsReporter.diagnosticsByFilePath.keys) {
for (diagnostic in diagnosticsReporter.diagnosticsByFilePath[filePath].orEmpty().sortedWith(SingleFileDiagnosticComparator)) {
var filePositionFinder: SequentialFilePositionFinder? = null
try {
when {
diagnostic is KtPsiDiagnostic -> diagnostic.element.location(diagnostic)
else -> {
if (filePositionFinder == null) {
filePositionFinder = SequentialFilePositionFinder(filePath)
}
// NOTE: SequentialPositionFinder relies on the ascending order of the input offsets, so the code relies
// on the the appropriate sorting above
// Also the end offset is ignored, as it is irrelevant for the CLI reporting
val position =
filePositionFinder.findNextPosition(DiagnosticUtils.firstRange(diagnostic.textRanges).startOffset)
MessageUtil.createMessageLocation(filePath, position.lineContent, position.line, position.column, -1, -1)
}
}?.let { location ->
reportDiagnostic(diagnostic, location, messageCollector)
hasErrors = hasErrors || diagnostic.severity == Severity.ERROR
}
} finally {
filePositionFinder?.close()
}
}
}
// reportSpecialErrors(diagnostics)
return hasErrors
}
@Suppress("UNUSED_PARAMETER")
private fun reportSpecialErrors(diagnostics: Collection<KtDiagnostic>) {
/*
@@ -40,15 +74,23 @@ object FirDiagnosticsCompilerResultsReporter {
private fun reportDiagnostic(diagnostic: KtDiagnostic, reporter: MessageCollector): Boolean {
if (!diagnostic.isValid) return false
diagnostic.location()?.let { location ->
val severity = AnalyzerWithCompilerReport.convertSeverity(diagnostic.severity)
// TODO: support multiple maps with messages
val renderer = FirDefaultErrorMessages.getRendererForDiagnostic(diagnostic)
reporter.report(severity, renderer.render(diagnostic), location)
diagnostic.location()?.let {
reportDiagnostic(diagnostic, it, reporter)
}
return diagnostic.severity == Severity.ERROR
}
private fun reportDiagnostic(
diagnostic: KtDiagnostic,
location: CompilerMessageSourceLocation,
reporter: MessageCollector
) {
val severity = AnalyzerWithCompilerReport.convertSeverity(diagnostic.severity)
// TODO: support multiple maps with messages
val renderer = FirDefaultErrorMessages.getRendererForDiagnostic(diagnostic)
reporter.report(severity, renderer.render(diagnostic), location)
}
private fun KtDiagnostic.location(): CompilerMessageSourceLocation? = when (val element = element) {
is KtPsiSourceElement -> element.location(this)
is KtLightSourceElement -> element.location(this)
@@ -76,7 +118,7 @@ object FirDiagnosticsCompilerResultsReporter {
override fun compare(o1: KtDiagnostic, o2: KtDiagnostic): Int {
val element1 = o1.element
val element2 = o1.element
// TODO: support light tree
// TODO: support light tree (likely obsolete, processing LT files in other code path)
if (element1 !is KtPsiSourceElement || element2 !is KtPsiSourceElement) return 0
val file1 = element1.psi.containingFile
@@ -93,4 +135,88 @@ object FirDiagnosticsCompilerResultsReporter {
} else o1.factory.name.compareTo(o2.factory.name)
}
}
private object SingleFileDiagnosticComparator : Comparator<KtDiagnostic> {
override fun compare(o1: KtDiagnostic, o2: KtDiagnostic): Int {
val range1 = DiagnosticUtils.firstRange(o1.textRanges)
val range2 = DiagnosticUtils.firstRange(o2.textRanges)
return if (range1 != range2) {
DiagnosticUtils.TEXT_RANGE_COMPARATOR.compare(range1, range2)
} else o1.factory.name.compareTo(o2.factory.name)
}
}
class SequentialFilePositionFinder(private val filePath: String?) : Closeable {
// TODO: verify if returning NONE on invalid files is the desired behavior (instead of throwing IOError)
private var reader: InputStreamReader? = filePath?.let(::File)?.takeIf { it.isFile }?.reader(/* TODO: select proper charset */)
private var currentLineContent: String? = null
private val buffer = CharArray(255)
private var bufLength = -1
private var bufPos = 0
private var endOfStream = false
private var skipNextLf = false
private var charsRead = 0
private var currentLine = 0
// assuming that if called multiple times, calls should be sorted by ascending offset
@Synchronized
fun findNextPosition(offset: Int, withLineContents: Boolean = true): PsiDiagnosticUtils.LineAndColumn {
if (offset < 0 || reader == null) return PsiDiagnosticUtils.LineAndColumn.NONE
assert(offset >= charsRead)
while (true) {
if (currentLineContent == null) {
currentLineContent = readNextLine()
}
val col = offset - (charsRead - currentLineContent!!.length - 1)/* beginning of line offset */ + 1 /* col is 1-based */
if (col <= currentLineContent!!.length) {
return PsiDiagnosticUtils.LineAndColumn(currentLine, col, if (withLineContents) currentLineContent else null)
}
if (endOfStream) return PsiDiagnosticUtils.LineAndColumn.NONE
currentLineContent = null
}
}
private fun readNextLine() = buildString {
while (true) {
if (bufPos >= bufLength) {
bufLength = reader!!.read(buffer)
bufPos = 0
if (bufLength < 0) {
endOfStream = true
break
}
} else {
val c = buffer[bufPos++]
charsRead++
when {
c == '\n' && skipNextLf -> {
skipNextLf = false
}
c == '\n' || c == '\r' -> {
currentLine++
skipNextLf = c == '\r'
break
}
else -> {
append(c)
skipNextLf = false
}
}
}
}
}
override fun close() {
reader?.close()
}
}
}
@@ -46,7 +46,19 @@ public class MessageUtil {
String path = virtualFile != null ? virtualFileToPath(virtualFile) : defaultValue;
PsiDiagnosticUtils.LineAndColumn start = range.getStart();
PsiDiagnosticUtils.LineAndColumn end = range.getEnd();
return CompilerMessageLocationWithRange.create(path, start.getLine(), start.getColumn(), end.getLine(), end.getColumn(), start.getLineContent());
return createMessageLocation(path, start.getLineContent(), start.getLine(), start.getColumn(), end.getLine(), end.getColumn());
}
@Nullable
public static CompilerMessageLocationWithRange createMessageLocation(
@Nullable String path,
@Nullable String lineContent,
int line,
int column,
int endLine,
int endColumn
) {
return CompilerMessageLocationWithRange.create(path, line, column, endLine, endColumn, lineContent);
}
@NotNull
@@ -23,8 +23,8 @@ import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CodegenFactory
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.fir.backend.Fir2IrResult
import org.jetbrains.kotlin.fir.backend.jvm.FirJvmBackendClassResolver
import org.jetbrains.kotlin.fir.backend.jvm.FirJvmBackendExtension
@@ -60,7 +60,6 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices
import org.jetbrains.kotlin.resolve.multiplatform.isCommonSource
import org.jetbrains.kotlin.utils.addToStdlib.flattenTo
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlin.utils.newLinkedHashMapWithExpectedSize
import java.io.File
@@ -244,16 +243,16 @@ object FirKotlinToJvmBytecodeCompiler {
val commonRawFir = commonSession?.buildFirFromKtFiles(commonKtFiles)
val rawFir = session.buildFirFromKtFiles(ktFiles)
val allKtDiagnostics = mutableListOf<KtDiagnostic>()
val diagnosticsReporter = DiagnosticReporterFactory.createReporter()
commonSession?.apply {
val (commonScopeSession, commonFir) = runResolution(commonRawFir!!)
runCheckers(commonScopeSession, commonFir).values.flattenTo(allKtDiagnostics)
runCheckers(commonScopeSession, commonFir, diagnosticsReporter)
}
val (scopeSession, fir) = session.runResolution(rawFir)
session.runCheckers(scopeSession, fir).values.flattenTo(allKtDiagnostics)
session.runCheckers(scopeSession, fir, diagnosticsReporter)
val hasErrors = FirDiagnosticsCompilerResultsReporter.reportDiagnostics(allKtDiagnostics, messageCollector)
val hasErrors = FirDiagnosticsCompilerResultsReporter.reportToCollector(diagnosticsReporter, messageCollector)
return if (syntaxErrors || hasErrors) null else FirResult(session, scopeSession, fir)
}