diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/messages/CompilerMessageLocation.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/messages/CompilerMessageLocation.kt index 1f3a6d349c5..ef0b175a16a 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/messages/CompilerMessageLocation.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/messages/CompilerMessageLocation.kt @@ -19,10 +19,12 @@ package org.jetbrains.kotlin.cli.common.messages import java.io.Serializable data class CompilerMessageLocation private constructor( - val path: String, - val line: Int, - val column: Int, - val lineContent: String? + val path: String, + val line: Int, + val column: Int, + val lineEnd: Int, + val columnEnd: Int, + val lineContent: String? ) : Serializable { override fun toString(): String = path + (if (line != -1 || column != -1) " ($line:$column)" else "") @@ -30,12 +32,23 @@ data class CompilerMessageLocation private constructor( companion object { @JvmStatic fun create(path: String?): CompilerMessageLocation? = - create(path, -1, -1, null) + create(path, -1, -1, null) @JvmStatic fun create(path: String?, line: Int, column: Int, lineContent: String?): CompilerMessageLocation? = - if (path == null) null else CompilerMessageLocation(path, line, column, lineContent) + if (path == null) null else CompilerMessageLocation(path, line, column, -1, -1, lineContent) - private val serialVersionUID: Long = 8228357578L + @JvmStatic + fun create( + path: String?, + lineStart: Int, + columnStart: Int, + lineEnd: Int?, + columnEnd: Int?, + lineContent: String? + ): CompilerMessageLocation? = + if (path == null) null else CompilerMessageLocation(path, lineStart, columnStart, lineEnd ?: -1, columnEnd ?: -1, lineContent) + + private val serialVersionUID: Long = 8228357579L } } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/DefaultDiagnosticReporter.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/DefaultDiagnosticReporter.kt index f560fc945f3..2d82644bf95 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/DefaultDiagnosticReporter.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/DefaultDiagnosticReporter.kt @@ -31,6 +31,6 @@ interface MessageCollectorBasedReporter : DiagnosticMessageReporter { override fun report(diagnostic: Diagnostic, file: PsiFile, render: String) = messageCollector.report( AnalyzerWithCompilerReport.convertSeverity(diagnostic.severity), render, - MessageUtil.psiFileToMessageLocation(file, file.name, DiagnosticUtils.getLineAndColumn(diagnostic)) + MessageUtil.psiFileToMessageLocation(file, file.name, DiagnosticUtils.getLineAndColumnRange(diagnostic)) ) } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/MessageUtil.java b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/MessageUtil.java index e82cb173e49..86350b5a15e 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/MessageUtil.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/MessageUtil.java @@ -35,18 +35,20 @@ public class MessageUtil { public static CompilerMessageLocation psiElementToMessageLocation(@Nullable PsiElement element) { if (element == null) return null; PsiFile file = element.getContainingFile(); - return psiFileToMessageLocation(file, "", DiagnosticUtils.getLineAndColumnInPsiFile(file, element.getTextRange())); + return psiFileToMessageLocation(file, "", DiagnosticUtils.getLineAndColumnRangeInPsiFile(file, element.getTextRange())); } @Nullable public static CompilerMessageLocation psiFileToMessageLocation( @NotNull PsiFile file, @Nullable String defaultValue, - @NotNull PsiDiagnosticUtils.LineAndColumn lineAndColumn + @NotNull PsiDiagnosticUtils.LineAndColumnRange range ) { VirtualFile virtualFile = file.getVirtualFile(); String path = virtualFile != null ? virtualFileToPath(virtualFile) : defaultValue; - return CompilerMessageLocation.create(path, lineAndColumn.getLine(), lineAndColumn.getColumn(), lineAndColumn.getLineContent()); + PsiDiagnosticUtils.LineAndColumn start = range.getStart(); + PsiDiagnosticUtils.LineAndColumn end = range.getEnd(); + return CompilerMessageLocation.create(path, start.getLine(), start.getColumn(), end.getLine(), end.getColumn(), start.getLineContent()); } @NotNull diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticUtils.java index 4a445b2768b..84669020945 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticUtils.java @@ -84,6 +84,24 @@ public class DiagnosticUtils { return PsiDiagnosticUtils.offsetToLineAndColumn(document, range.getStartOffset()); } + @NotNull + public static PsiDiagnosticUtils.LineAndColumnRange getLineAndColumnRange(@NotNull Diagnostic diagnostic) { + PsiFile file = diagnostic.getPsiFile(); + List textRanges = diagnostic.getTextRanges(); + if (textRanges.isEmpty()) return PsiDiagnosticUtils.LineAndColumnRange.NONE; + TextRange firstRange = firstRange(textRanges); + return getLineAndColumnRangeInPsiFile(file, firstRange); + } + + @NotNull + public static PsiDiagnosticUtils.LineAndColumnRange getLineAndColumnRangeInPsiFile(PsiFile file, TextRange range) { + Document document = file.getViewProvider().getDocument(); + return new PsiDiagnosticUtils.LineAndColumnRange( + PsiDiagnosticUtils.offsetToLineAndColumn(document, range.getStartOffset()), + PsiDiagnosticUtils.offsetToLineAndColumn(document, range.getEndOffset()) + ); + } + public static void throwIfRunningOnServer(Throwable e) { // This is needed for the Web Demo server to log the exceptions coming from the analyzer instead of showing them in the editor. if (System.getProperty("kotlin.running.in.server.mode", "false").equals("true") || ApplicationManager.getApplication().isUnitTestMode()) { diff --git a/compiler/psi/src/org/jetbrains/kotlin/diagnostics/PsiDiagnosticUtils.java b/compiler/psi/src/org/jetbrains/kotlin/diagnostics/PsiDiagnosticUtils.java index aa52b21a708..683c7474fba 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/diagnostics/PsiDiagnosticUtils.java +++ b/compiler/psi/src/org/jetbrains/kotlin/diagnostics/PsiDiagnosticUtils.java @@ -116,4 +116,35 @@ public class PsiDiagnosticUtils { return "(" + line + "," + column + ")"; } } + + public static final class LineAndColumnRange { + + public static final LineAndColumnRange NONE = new LineAndColumnRange(LineAndColumn.NONE, LineAndColumn.NONE); + + private final LineAndColumn start; + private final LineAndColumn end; + + public LineAndColumnRange(LineAndColumn start, LineAndColumn end) { + this.start = start; + this.end = end; + } + + public LineAndColumn getStart() { + return start; + } + + public LineAndColumn getEnd() { + return end; + } + + // NOTE: This method is used for presenting positions to the user + @Override + public String toString() { + if (start.line == end.line) { + return "(" + start.line + "," + start.column + "-" + end.column + ")"; + } + + return start.toString() + " - " + end.toString(); + } + } } diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt index e288d362582..35e8d638b7e 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.runBlocking import org.jetbrains.kotlin.cli.common.repl.BasicReplStageHistory import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerImpl -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.junit.Assert import org.junit.Test import kotlin.script.experimental.api.* @@ -96,6 +95,27 @@ class ReplTest : TestCase() { ) } + @Test + fun testEvalWithErrorWithLocation() { + checkEvaluateInReplDiags( + sequenceOf( + """ + val foobar = 78 + val foobaz = "dsdsda" + val ddd = ppp + val ooo = foobar + """.trimIndent() + ), + sequenceOf( + makeFailureResult( + "Unresolved reference: ppp", location = SourceCode.Location( + SourceCode.Position(3, 11), SourceCode.Position(3, 14) + ) + ) + ) + ) + } + @Test fun testSyntaxErrors() { checkEvaluateInReplDiags( @@ -152,7 +172,8 @@ fun evaluateInRepl( var currentEvalConfig = evaluationConfiguration ?: ScriptEvaluationConfiguration() val snipetsLimited = if (limit == 0) snippets else snippets.take(limit) return snipetsLimited.mapIndexed { snippetNo, snippetText -> - val snippetSource = snippetText.toScriptSource("Line_$snippetNo.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}") + val snippetSource = + snippetText.toScriptSource("Line_$snippetNo.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}") val snippetId = ReplSnippetIdImpl(snippetNo, 0, snippetSource) replCompilerProxy.compileReplSnippet(compilationState, snippetSource, snippetId, compilationHistory) .onSuccess { @@ -188,9 +209,15 @@ fun checkEvaluateInReplDiags( when { res is ResultWithDiagnostics.Failure && expectedRes is ResultWithDiagnostics.Failure -> { Assert.assertTrue( - "#$index: Expected $expectedRes, got $res", + "#$index: Expected $expectedRes, got $res. Messages are different", res.reports.map { it.message } == expectedRes.reports.map { it.message } ) + Assert.assertTrue( + "#$index: Expected $expectedRes, got $res. Locations are different", + res.reports.map { it.location }.zip(expectedRes.reports.map { it.location }).all { + it.second == null || it.second == it.first + } + ) } res is ResultWithDiagnostics.Success && expectedRes is ResultWithDiagnostics.Success -> { val expectedVal = expectedRes.value diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt index bfb7a520305..27e724336e2 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt @@ -37,10 +37,20 @@ internal class ScriptDiagnosticsMessageCollector(private val parentMessageCollec if (mappedSeverity != null) { val mappedLocation = location?.let { if (it.line < 0 && it.column < 0) null // special location created by CompilerMessageLocation.create + else if (it.lineEnd < 0 && it.columnEnd < 0) SourceCode.Location( + SourceCode.Position( + it.line, + it.column + ) + ) else SourceCode.Location( SourceCode.Position( it.line, it.column + ), + SourceCode.Position( + it.lineEnd, + it.columnEnd ) ) }