Fix stale BSP diagnostics

The BSP server didn't reset old diagnostic messages sent to BSP clients under
certain circumstances. This commit mitigates this edge case and ensures that
diagnostics for files that previously had compilation problems are properly
reset when fresh diagnostics messages are sent.

The culprit was a mismatch of map keys: Files with problems were sometimes recorded
under an absolute path, but later attempted to be retrieved by virtual path.
This commit is contained in:
SlowBrainDude 2024-07-24 02:45:24 +02:00 committed by Friendseeker
parent 3decafbf43
commit b1b5caa1ca
2 changed files with 81 additions and 3 deletions

View File

@ -25,6 +25,7 @@ import xsbti.{
import scala.jdk.CollectionConverters.*
import scala.collection.mutable
import java.nio.file.Path
/**
* Provides methods for sending success and failure reports and publishing diagnostics.
@ -88,7 +89,7 @@ final class BuildServerReporterImpl(
import sbt.internal.inc.JavaInterfaceUtil._
private lazy val exchange = StandardMain.exchange
private val problemsByFile = mutable.Map[VirtualFileRef, Vector[Problem]]()
private val problemsByFile = mutable.Map[Path, Vector[Problem]]()
// sometimes the compiler returns a fake position such as <macro>
// on Windows, this causes InvalidPathException (see #5994 and #6720)
@ -112,9 +113,10 @@ final class BuildServerReporterImpl(
override def sendFailureReport(sources: Array[VirtualFile]): Unit = {
for (source <- sources) {
val problems = problemsByFile.getOrElse(source, Vector.empty)
val problems = problemsByFile.getOrElse(converter.toPath(source), Vector.empty)
sendReport(source, problems)
}
notifyFirstReport()
}
private def sendReport(source: VirtualFileRef, problems: Vector[Problem]): Unit = {
@ -150,7 +152,11 @@ final class BuildServerReporterImpl(
id <- problem.position.sourcePath.toOption
(document, diagnostic) <- mapProblemToDiagnostic(problem)
} {
val fileRef = VirtualFileRef.of(id)
// Note: We're putting the real path in `fileRef` because the `id` String can take
// two forms, either a ${something}/relativePath, or the absolute path of the source.
// But where we query this, we always have _only_ a ${something}/relativePath available.
// So here we "normalize" to the real path.
val fileRef = converter.toPath(VirtualFileRef.of(id))
problemsByFile(fileRef) = problemsByFile.getOrElse(fileRef, Vector.empty) :+ problem
val params = PublishDiagnosticsParams(

View File

@ -252,6 +252,78 @@ class BuildServerTest extends AbstractServerTest {
)
}
test("buildTarget/compile [Java diagnostics] clear stale warnings") {
val buildTarget = buildTargetUri("javaProj", "Compile")
val testFile = new File(svr.baseDirectory, s"java-proj/src/main/java/example/Hello.java")
val otherBuildFile = new File(svr.baseDirectory, "force-java-out-of-process-compiler.sbt")
// Setting `javaHome` will force SBT to shell out to an external Java compiler instead
// of using the local compilation service offered by the JVM running this SBT instance.
IO.write(
otherBuildFile,
"""
|lazy val javaProj = project
| .in(file("java-proj"))
| .settings(
| javacOptions += "-Xlint:all",
| javaHome := Some(file(System.getProperty("java.home")))
| )
|""".stripMargin
)
reloadWorkspace()
compile(buildTarget)
assertMessage(
"build/publishDiagnostics",
"Hello.java",
""""severity":2""",
"""found raw type: List"""
)(message = "should send publishDiagnostics with severity 2 for Hello.java")
assertMessage(
"build/publishDiagnostics",
"Hello.java",
""""severity":1""",
"""incompatible types: int cannot be converted to String"""
)(
message = "should send publishDiagnostics with severity 1 for Hello.java"
)
// Note the messages changed slightly in both cases. That's interesting
IO.write(
testFile,
"""|package example;
|
|import java.util.List;
|import java.util.ArrayList;
|
|class Hello {
| public static void main(String[] args) {
| List<String> list = new ArrayList<>();
| String msg = "42";
| System.out.println(msg);
| }
|}
|""".stripMargin
)
compile(buildTarget)
assertMessage(
"build/publishDiagnostics",
"Hello.java",
"\"diagnostics\":[]",
"\"reset\":true"
)(
message = "should send publishDiagnostics with empty diagnostics"
)
IO.delete(otherBuildFile)
reloadWorkspace()
()
}
test("buildTarget/scalacOptions, buildTarget/javacOptions") {
val buildTargets = Seq(
buildTargetUri("util", "Compile"),