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 0c542d1fff6..fd5c2ff3355 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 @@ -22,6 +22,8 @@ interface CompilerMessageSourceLocation : Serializable { val path: String val line: Int val column: Int + val lineEnd: Int get() = -1 + val columnEnd: Int get() = -1 val lineContent: String? } @@ -46,3 +48,31 @@ data class CompilerMessageLocation private constructor( private val serialVersionUID: Long = 8228357578L } } + +data class CompilerMessageLocationWithRange private constructor( + override val path: String, + override val line: Int, + override val column: Int, + override val lineEnd: Int, + override val columnEnd: Int, + override val lineContent: String? +) : CompilerMessageSourceLocation { + override fun toString(): String = + path + (if (line != -1 || column != -1) " ($line:$column)" else "") + + companion object { + @JvmStatic + fun create( + path: String?, + lineStart: Int, + columnStart: Int, + lineEnd: Int?, + columnEnd: Int?, + lineContent: String? + ): CompilerMessageLocationWithRange? = + if (path == null) null else CompilerMessageLocationWithRange(path, lineStart, columnStart, lineEnd ?: -1, columnEnd ?: -1, lineContent) + + private val serialVersionUID: Long = 8228357578L + } +} + 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..03f291ec68f 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 @@ -32,21 +32,23 @@ public class MessageUtil { private MessageUtil() {} @Nullable - public static CompilerMessageLocation psiElementToMessageLocation(@Nullable PsiElement element) { + public static CompilerMessageSourceLocation 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( + public static CompilerMessageSourceLocation 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 CompilerMessageLocationWithRange.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 3f49a6826c9..ea28e2bae81 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 @@ -100,7 +100,9 @@ class ReplTest : TestCase() { ), sequenceOf( makeFailureResult( - "Unresolved reference: ppp", location = SourceCode.Location(SourceCode.Position(3, 11)) + "Unresolved reference: ppp", location = SourceCode.Location( + SourceCode.Position(3, 11), SourceCode.Position(3, 14) + ) ) ) ) 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 96b5a0a7923..c1e2225bbb6 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 @@ class ScriptDiagnosticsMessageCollector(private val parentMessageCollector: Mess 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 ) ) } diff --git a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt index ac1f5781c45..230f0a7d2c6 100644 --- a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt +++ b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt @@ -6,7 +6,7 @@ package org.jetbrains.kotlin.scripting.ide_services import junit.framework.TestCase -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocationWithRange import org.jetbrains.kotlin.scripting.ide_services.test_util.JvmTestRepl import org.jetbrains.kotlin.scripting.ide_services.test_util.SourceCodeTestImpl import java.io.File @@ -86,6 +86,8 @@ class JvmIdeServicesTest : TestCase() { } else { assertEquals(3, loc.line) assertEquals(11, loc.column) + assertEquals(3, loc.lineEnd) + assertEquals(14, loc.columnEnd) } } else { fail("Result should be an error") @@ -115,6 +117,8 @@ class JvmIdeServicesTest : TestCase() { } else { assertEquals(3, loc.line) assertEquals(13, loc.column) + assertEquals(3, loc.lineEnd) + assertEquals(16, loc.columnEnd) } } else { fail("Result should be an error") @@ -383,17 +387,19 @@ private fun checkCompile(repl: JvmTestRepl, line: String): LinkedSnippet ResultWithDiagnostics.getErrors(): CompilationErrors = CompilationErrors( reports.joinToString("\n") { report -> report.location?.let { loc -> - CompilerMessageLocation.create( + CompilerMessageLocationWithRange.create( report.sourcePath, loc.start.line, loc.start.col, + loc.end?.line, + loc.end?.col, null )?.toString()?.let { "$it " @@ -408,10 +414,12 @@ private fun ResultWithDiagnostics.getErrors(): CompilationErrors = } }?.let { val loc = it.location ?: return@let null - CompilerMessageLocation.create( + CompilerMessageLocationWithRange.create( it.sourcePath, loc.start.line, loc.start.col, + loc.end?.line, + loc.end?.col, null ) } diff --git a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt index c2235ea1abe..df205d961a8 100644 --- a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt +++ b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt @@ -189,10 +189,10 @@ class ReplCompletionAndErrorsAnalysisTest : TestCase() { val c = foob """.trimIndent() expect { - addError(1, 16, "Type mismatch: inferred type is String but Int was expected", "ERROR") - addError(1, 22, "The floating-point literal does not conform to the expected type String", "ERROR") - addError(2, 14, "Type mismatch: inferred type is String but Int was expected", "ERROR") - addError(3, 9, "Unresolved reference: foob", "ERROR") + addError(1, 16, 1, 20, "Type mismatch: inferred type is String but Int was expected", "ERROR") + addError(1, 22, 1, 26, "The floating-point literal does not conform to the expected type String", "ERROR") + addError(2, 14, 2, 19, "Type mismatch: inferred type is String but Int was expected", "ERROR") + addError(3, 9, 3, 13, "Unresolved reference: foob", "ERROR") } } } @@ -341,14 +341,15 @@ class ReplCompletionAndErrorsAnalysisTest : TestCase() { } val errors = ExpectedList(run::doErrorCheck) - fun addError(startLine: Int, startCol: Int, message: String, severity: String) { + fun addError(startLine: Int, startCol: Int, endLine: Int, endCol: Int, message: String, severity: String) { errors.add( ScriptDiagnostic( ScriptDiagnostic.unspecifiedError, message, ScriptDiagnostic.Severity.valueOf(severity), location = SourceCode.Location( - SourceCode.Position(startLine, startCol) + SourceCode.Position(startLine, startCol), + SourceCode.Position(endLine, endCol) ) ) )