Skip to content

Commit

Permalink
bugfix: Fix showing Scala 3 diagnostics (#544)
Browse files Browse the repository at this point in the history
* bugfix: Fix showing Scala 3 diagnostics

Previously, Scala 3 diagnsotics would not be shown since they currently use rendered message, which is a pretty printed diagnotic. Now, I added a separate parser for Scala 3 compiler diagnostics.

* chore: Update changelog with information about the change
  • Loading branch information
tgodzik committed Mar 27, 2024
1 parent 9ec2d80 commit 3629a3f
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -47,6 +47,8 @@
| [1c51f02](https://github.com/JetBrains/bazel-bsp/commit/1c51f02a4331c331a0d7d4cc412bfd1e36daf77e)
- Server adds sources to generated libs.
| [eaa5161](https://github.com/JetBrains/bazel-bsp/commit/eaa5161fe4193268c21c324f27786f5f17f79afd)
- Support Scala 3 diagnostics.
| [744735f](https://github.com/JetBrains/bazel-bsp/commit/744735ff30c96f218414514e99019c8ffc700dfe)

## [3.0.0] - 09.08.2023

Expand Down
Expand Up @@ -56,5 +56,5 @@ object BazelRootMessageParser : Parser {
}

private fun collectCompilerDiagnostics(output: Output) =
generateSequence { CompilerDiagnosticParser.tryParseOne(output) }.toList()
generateSequence { CompilerDiagnosticParser.tryParseOne(output) ?: Scala3CompilerDiagnosticParser.tryParseOne(output) }.toList()
}
Expand Up @@ -51,6 +51,7 @@ class DiagnosticsParser {
private val Parsers = listOf(
BazelRootMessageParser,
CompilerDiagnosticParser,
Scala3CompilerDiagnosticParser,
AllCatchParser
)
private val IgnoredLines = listOf(
Expand Down
@@ -0,0 +1,55 @@
package org.jetbrains.bsp.bazel.server.diagnostics

object Scala3CompilerDiagnosticParser : Parser {

override fun tryParse(output: Output): List<Diagnostic> =
listOfNotNull(tryParseOne(output))

private val DiagnosticHeader = """
^--\ # "-- " diagnostic start
\[E\d+\] # "[E008]" code
([^:]+): # (1) type of diagnostic
([^:]+):(\d+):(\d+) # (2) path, (3) line, (4) column
\ \-+$ # " -----------------" ending
""".toRegex(RegexOption.COMMENTS)

// Scala 3 diagnostics have additional color printed, since Bazel uses renderedMessage field
private val colorRegex = "\u001b\\[1A\u001b\\[K|\u001B\\[[;\\d]*m".toRegex()

fun tryTake(output: Output, regex: Regex): MatchResult? =
output.peek()?.let { regex.matchEntire(it.replace(colorRegex, "")) }?.also { output.take() }

fun tryParseOne(output: Output): Diagnostic? {
return tryTake(output, DiagnosticHeader)
?.let { match ->
val level = if (match.groupValues[1].contains("Error")) Level.Error else Level.Warning
val path = match.groupValues[2].trim()
val line = match.groupValues[3].toInt()
val messageLines = collectMessageLines(match.groupValues[1].trim(), output)
val column = match.groupValues[4].toIntOrNull() ?: tryFindColumnNumber(messageLines) ?: 1
val message = messageLines.joinToString("\n")
Diagnostic(Position(line, column), message, level, path, output.targetLabel)
}
}

private fun collectMessageLines(header: String, output: Output): List<String> {
val lines = mutableListOf<String>()
fun String.cleanLine(): String =
this.replace(colorRegex, "").trim()

// skip lines with numbers which show the source and skip the next ^^^^ line
if (output.peek()?.cleanLine()?.startsWith('|') == false) output.take(2)
while (output.nonEmpty() && output.peek()?.cleanLine()?.startsWith('|') == true) {
lines.add(output.take().cleanLine().removePrefix("|").trim())
}
lines.add(0, header)
return lines
}

private val IssuePositionMarker = """^[\s\|]*\^\s*$""".toRegex() // ^ surrounded by whitespace only

private fun tryFindColumnNumber(messageLines: List<String>): Int? {
val line = messageLines.find { IssuePositionMarker.matches(it.replace(colorRegex, "")) }
return line?.indexOf("^")?.let { it + 1 }
}
}
Expand Up @@ -358,6 +358,106 @@ class DiagnosticsServiceTest {
)
diagnostics shouldContainExactlyInAnyOrder expected
}
@Test
fun `parse scala 3 error`() {
// given
val output = """
|-- [E007] Type Mismatch Error: Hello.scala:19:20 -------------------------------
|19 | def hello4: Int = "Hello"
| | ^^^^^^^
| | Found: ("Hello" : String)
| | Required: Int
| |
| | longer explanation available when compiling with `-explain`
|2 errors found
|Build failed
|java.lang.RuntimeException: Build failed
| at io.bazel.rulesscala.scalac.ScalacInvoker.invokeCompiler(ScalacInvoker3.java:43)
| at io.bazel.rulesscala.scalac.ScalacWorker.compileScalaSources(ScalacWorker.java:253)
| at io.bazel.rulesscala.scalac.ScalacWorker.work(ScalacWorker.java:69)
| at io.bazel.rulesscala.worker.Worker.persistentWorkerMain(Worker.java:86)
| at io.bazel.rulesscala.worker.Worker.workerMain(Worker.java:39)
| at io.bazel.rulesscala.scalac.ScalacWorker.main(ScalacWorker.java:33)
""".trimMargin()

// when
val diagnostics = extractDiagnostics(output, "//project/src/main/scala/com/example/project:project")

// then
val expected = listOf(
PublishDiagnosticsParams(
TextDocumentIdentifier("file:///user/workspace/Hello.scala"),
BuildTargetIdentifier("//project/src/main/scala/com/example/project:project"),
ErrorDiagnostic(
Position(19, 20),
"""|Type Mismatch Error
|Found: ("Hello" : String)
|Required: Int
|
|longer explanation available when compiling with `-explain`""".trimMargin()
)
)
)
diagnostics shouldContainExactlyInAnyOrder expected
}

@Test
fun `parse scala 3 multiple errors`() {
// given
val output = """
|-- [E007] Type Mismatch Error: Hello.scala:18:20 -------------------------------
|18 | def hello3: Int = "Hello"
| | ^^^^^^^
| | Found: ("Hello" : String)
| | Required: Int
| |
| | longer explanation available when compiling with `-explain`
|-- [E007] Type Mismatch Error: Hello.scala:19:20 -------------------------------
|19 | def hello4: Int = "Hello"
| | ^^^^^^^
| | Found: ("Hello" : String)
| | Required: Int
| |
| | longer explanation available when compiling with `-explain`
|2 errors found
|Build failed
|java.lang.RuntimeException: Build failed
| at io.bazel.rulesscala.scalac.ScalacInvoker.invokeCompiler(ScalacInvoker3.java:43)
| at io.bazel.rulesscala.scalac.ScalacWorker.compileScalaSources(ScalacWorker.java:253)
| at io.bazel.rulesscala.scalac.ScalacWorker.work(ScalacWorker.java:69)
| at io.bazel.rulesscala.worker.Worker.persistentWorkerMain(Worker.java:86)
| at io.bazel.rulesscala.worker.Worker.workerMain(Worker.java:39)
|at io.bazel.rulesscala.scalac.ScalacWorker.main(ScalacWorker.java:33)
""".trimMargin()

// when
val diagnostics = extractDiagnostics(output, "//project/src/main/scala/com/example/project:project")

// then
val expected = listOf(
PublishDiagnosticsParams(
TextDocumentIdentifier("file:///user/workspace/Hello.scala"),
BuildTargetIdentifier("//project/src/main/scala/com/example/project:project"),
ErrorDiagnostic(
Position(18, 20),
"""|Type Mismatch Error
|Found: ("Hello" : String)
|Required: Int
|
|longer explanation available when compiling with `-explain`""".trimMargin()
),
ErrorDiagnostic(
Position(19, 20),
"""|Type Mismatch Error
|Found: ("Hello" : String)
|Required: Int
|
|longer explanation available when compiling with `-explain`""".trimMargin()
)
)
)
diagnostics shouldContainExactlyInAnyOrder expected
}

@Test
fun `parse scala warnings`() {
Expand Down

0 comments on commit 3629a3f

Please sign in to comment.