Skip to content

Commit 9688740

Browse files
authored
Adjust actionable diagnostics for scripts (#2815)
* Adjust actionable diagnostics for scripts * Add test for 2.13, and make them run accordingly * Change test format, remove old test * Revert changes made by metalsOrganizeImports
1 parent bbb9a71 commit 9688740

File tree

3 files changed

+169
-95
lines changed

3 files changed

+169
-95
lines changed

modules/build/src/main/scala/scala/build/bsp/BspClient.scala

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package scala.build.bsp
22

33
import ch.epfl.scala.bsp4j.{ScalaAction, ScalaDiagnostic, ScalaTextEdit, ScalaWorkspaceEdit}
44
import ch.epfl.scala.bsp4j as b
5-
import com.google.gson.Gson
5+
import com.google.gson.{Gson, JsonElement}
66

77
import java.lang.Boolean as JBoolean
88
import java.net.URI
@@ -48,6 +48,24 @@ class BspClient(
4848
diag0.getRange.getStart.setLine(startLine)
4949
diag0.getRange.getEnd.setLine(endLine)
5050

51+
val scalaDiagnostic = new Gson().fromJson[b.ScalaDiagnostic](
52+
diag0.getData().asInstanceOf[JsonElement],
53+
classOf[b.ScalaDiagnostic]
54+
)
55+
56+
scalaDiagnostic.getActions().asScala.foreach { action =>
57+
for {
58+
change <- action.getEdit().getChanges().asScala
59+
startLine <- updateLine(change.getRange.getStart.getLine)
60+
endLine <- updateLine(change.getRange.getEnd.getLine)
61+
} yield {
62+
change.getRange().getStart.setLine(startLine)
63+
change.getRange().getEnd.setLine(endLine)
64+
}
65+
}
66+
67+
diag0.setData(scalaDiagnostic)
68+
5169
if (
5270
diag0.getMessage.contains(
5371
"cannot be a main method since it cannot be accessed statically"

modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala

Lines changed: 75 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,7 +1589,6 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg
15891589
val visibleDiagnostics =
15901590
localClient.diagnostics().takeWhile(!_.getReset).flatMap(_.getDiagnostics.asScala)
15911591

1592-
expect(visibleDiagnostics.nonEmpty)
15931592
expect(visibleDiagnostics.length == 1)
15941593

15951594
val updateActionableDiagnostic = visibleDiagnostics.head
@@ -1625,6 +1624,77 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg
16251624
}
16261625
}
16271626
}
1627+
1628+
if (actualScalaVersion.startsWith("3."))
1629+
List(".sc", ".scala").foreach { filetype =>
1630+
test(s"bsp should report actionable diagnostic from bloop for $filetype files (Scala 3)") {
1631+
val fileName = s"Hello$filetype"
1632+
val inputs = TestInputs(
1633+
os.rel / fileName ->
1634+
s"""
1635+
|object Hello {
1636+
| sealed trait TestTrait
1637+
| case class TestA() extends TestTrait
1638+
| case class TestB() extends TestTrait
1639+
| val traitInstance: TestTrait = ???
1640+
| traitInstance match {
1641+
| case TestA() =>
1642+
| }
1643+
|}
1644+
|""".stripMargin
1645+
)
1646+
withBsp(inputs, Seq(".")) {
1647+
(_, localClient, remoteServer) =>
1648+
async {
1649+
// prepare build
1650+
val buildTargetsResp = await(remoteServer.workspaceBuildTargets().asScala)
1651+
// build code
1652+
val targets = buildTargetsResp.getTargets.asScala.map(_.getId()).asJava
1653+
await(remoteServer.buildTargetCompile(new b.CompileParams(targets)).asScala)
1654+
1655+
val visibleDiagnostics =
1656+
localClient.diagnostics().map(_.getDiagnostics().asScala).find(
1657+
!_.isEmpty
1658+
).getOrElse(
1659+
Nil
1660+
)
1661+
1662+
expect(visibleDiagnostics.size == 1)
1663+
1664+
val updateActionableDiagnostic = visibleDiagnostics.head
1665+
1666+
checkDiagnostic(
1667+
diagnostic = updateActionableDiagnostic,
1668+
expectedMessage = "match may not be exhaustive.",
1669+
expectedSeverity = b.DiagnosticSeverity.WARNING,
1670+
expectedStartLine = 6,
1671+
expectedStartCharacter = 2,
1672+
expectedEndLine = 6,
1673+
expectedEndCharacter = 15,
1674+
expectedSource = Some("bloop"),
1675+
strictlyCheckMessage = false
1676+
)
1677+
1678+
val scalaDiagnostic = new Gson().fromJson[b.ScalaDiagnostic](
1679+
updateActionableDiagnostic.getData().asInstanceOf[JsonElement],
1680+
classOf[b.ScalaDiagnostic]
1681+
)
1682+
1683+
val actions = scalaDiagnostic.getActions().asScala.toList
1684+
assert(actions.size == 1)
1685+
val changes = actions.head.getEdit().getChanges().asScala.toList
1686+
assert(changes.size == 1)
1687+
val textEdit = changes.head
1688+
1689+
expect(textEdit.getNewText().contains("\n case TestB() => ???"))
1690+
expect(textEdit.getRange().getStart.getLine == 7)
1691+
expect(textEdit.getRange().getStart.getCharacter == 19)
1692+
expect(textEdit.getRange().getEnd.getLine == 7)
1693+
expect(textEdit.getRange().getEnd.getCharacter == 19)
1694+
}
1695+
}
1696+
}
1697+
}
16281698
test("bsp should support jvmRunEnvironment request") {
16291699
val inputs = TestInputs(
16301700
os.rel / "Hello.scala" ->
@@ -1818,95 +1888,6 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg
18181888
)
18191889
}
18201890

1821-
if (actualScalaVersion.startsWith("3."))
1822-
test("actionable diagnostics from compiler") {
1823-
val inputs = TestInputs(
1824-
os.rel / "test.sc" ->
1825-
"""//> using scala 3.3.2-RC1-bin-20230723-5afe621-NIGHTLY
1826-
|def foo(): Int = 23
1827-
|
1828-
|def test: Int = foo
1829-
|// ^^^ error: missing parentheses
1830-
|""".stripMargin
1831-
)
1832-
1833-
withBsp(inputs, Seq(".")) { (root, localClient, remoteServer) =>
1834-
async {
1835-
val buildTargetsResp = await(remoteServer.workspaceBuildTargets().asScala)
1836-
val target = {
1837-
val targets = buildTargetsResp.getTargets.asScala.map(_.getId).toSeq
1838-
expect(targets.length == 2)
1839-
extractMainTargets(targets)
1840-
}
1841-
1842-
val targetUri = TestUtil.normalizeUri(target.getUri)
1843-
checkTargetUri(root, targetUri)
1844-
1845-
val targets = List(target).asJava
1846-
1847-
val compileResp = await {
1848-
remoteServer
1849-
.buildTargetCompile(new b.CompileParams(targets))
1850-
.asScala
1851-
}
1852-
expect(compileResp.getStatusCode == b.StatusCode.ERROR)
1853-
1854-
val diagnosticsParams = {
1855-
val diagnostics = localClient.diagnostics()
1856-
val params = diagnostics(2)
1857-
expect(params.getBuildTarget.getUri == targetUri)
1858-
expect(
1859-
TestUtil.normalizeUri(params.getTextDocument.getUri) ==
1860-
TestUtil.normalizeUri((root / "test.sc").toNIO.toUri.toASCIIString)
1861-
)
1862-
params
1863-
}
1864-
1865-
val diagnostics = diagnosticsParams.getDiagnostics.asScala
1866-
expect(diagnostics.size == 1)
1867-
1868-
val theDiagnostic = diagnostics.head
1869-
1870-
checkDiagnostic(
1871-
diagnostic = theDiagnostic,
1872-
expectedMessage =
1873-
"method foo in class test$_ must be called with () argument",
1874-
expectedSeverity = b.DiagnosticSeverity.ERROR,
1875-
expectedStartLine = 3,
1876-
expectedStartCharacter = 16,
1877-
expectedEndLine = 3,
1878-
expectedEndCharacter = 19
1879-
)
1880-
1881-
// Shouldn't dataKind be set to "scala"? expect(theDiagnostic.getDataKind == "scala")
1882-
1883-
val gson = new com.google.gson.Gson()
1884-
1885-
val scalaDiagnostic: b.ScalaDiagnostic = gson.fromJson(
1886-
theDiagnostic.getData.toString,
1887-
classOf[b.ScalaDiagnostic]
1888-
)
1889-
1890-
val actions = scalaDiagnostic.getActions.asScala
1891-
expect(actions.size == 1)
1892-
1893-
val action = actions.head
1894-
expect(action.getTitle == "Insert ()")
1895-
1896-
val edit = action.getEdit
1897-
expect(edit.getChanges.asScala.size == 1)
1898-
val change = edit.getChanges.asScala.head
1899-
1900-
val expectedRange = new b.Range(
1901-
new b.Position(9, 19),
1902-
new b.Position(9, 19)
1903-
)
1904-
expect(change.getRange == expectedRange)
1905-
expect(change.getNewText == "()")
1906-
}
1907-
}
1908-
}
1909-
19101891
if (!actualScalaVersion.startsWith("2.12"))
19111892
test("actionable diagnostics on deprecated using directives") {
19121893
val inputs = TestInputs(
@@ -2074,7 +2055,7 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg
20742055
params
20752056
}
20762057

2077-
private def checkDiagnostic(
2058+
protected def checkDiagnostic(
20782059
diagnostic: b.Diagnostic,
20792060
expectedMessage: String,
20802061
expectedSeverity: b.DiagnosticSeverity,
@@ -2090,10 +2071,11 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg
20902071
expect(diagnostic.getRange.getStart.getCharacter == expectedStartCharacter)
20912072
expect(diagnostic.getRange.getEnd.getLine == expectedEndLine)
20922073
expect(diagnostic.getRange.getEnd.getCharacter == expectedEndCharacter)
2074+
val message = TestUtil.removeAnsiColors(diagnostic.getMessage)
20932075
if (strictlyCheckMessage)
2094-
assertNoDiff(diagnostic.getMessage, expectedMessage)
2076+
assertNoDiff(message, expectedMessage)
20952077
else
2096-
expect(diagnostic.getMessage.contains(expectedMessage))
2078+
expect(message.contains(expectedMessage))
20972079
for (es <- expectedSource)
20982080
expect(diagnostic.getSource == es)
20992081
}
Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,77 @@
11
package scala.cli.integration
22

3-
class BspTests213 extends BspTestDefinitions with Test213
3+
import ch.epfl.scala.bsp4j as b
4+
import com.eed3si9n.expecty.Expecty.expect
5+
import com.google.gson.{Gson, JsonElement}
6+
7+
import scala.async.Async.{async, await}
8+
import scala.concurrent.ExecutionContext.Implicits.global
9+
import scala.jdk.CollectionConverters.*
10+
11+
class BspTests213 extends BspTestDefinitions with Test213 {
12+
13+
List(".sc", ".scala").foreach { filetype =>
14+
test(s"bsp should report actionable diagnostic from bloop for $filetype files (Scala 2.13)") {
15+
val fileName = s"Hello$filetype"
16+
val inputs = TestInputs(
17+
os.rel / fileName ->
18+
s"""
19+
|object Hello {
20+
| def foo: Any = {
21+
| x: Int => x * 2
22+
| }
23+
|}
24+
|""".stripMargin
25+
)
26+
withBsp(inputs, Seq(".", "-O", "-Xsource:3")) {
27+
(_, localClient, remoteServer) =>
28+
async {
29+
// prepare build
30+
val buildTargetsResp = await(remoteServer.workspaceBuildTargets().asScala)
31+
// build code
32+
val targets = buildTargetsResp.getTargets.asScala.map(_.getId()).asJava
33+
await(remoteServer.buildTargetCompile(new b.CompileParams(targets)).asScala)
34+
35+
val visibleDiagnostics =
36+
localClient.diagnostics().map(_.getDiagnostics().asScala).find(!_.isEmpty).getOrElse(
37+
Nil
38+
)
39+
40+
expect(visibleDiagnostics.size == 1)
41+
42+
val updateActionableDiagnostic = visibleDiagnostics.head
43+
44+
checkDiagnostic(
45+
diagnostic = updateActionableDiagnostic,
46+
expectedMessage = "parentheses are required around the parameter of a lambda",
47+
expectedSeverity = b.DiagnosticSeverity.ERROR,
48+
expectedStartLine = 3,
49+
expectedStartCharacter = 5,
50+
expectedEndLine = 3,
51+
expectedEndCharacter = 5,
52+
expectedSource = Some("bloop"),
53+
strictlyCheckMessage = false
54+
)
55+
56+
val scalaDiagnostic = new Gson().fromJson[b.ScalaDiagnostic](
57+
updateActionableDiagnostic.getData().asInstanceOf[JsonElement],
58+
classOf[b.ScalaDiagnostic]
59+
)
60+
61+
val actions = scalaDiagnostic.getActions().asScala.toList
62+
assert(actions.size == 1)
63+
val changes = actions.head.getEdit().getChanges().asScala.toList
64+
assert(changes.size == 1)
65+
val textEdit = changes.head
66+
67+
expect(textEdit.getNewText().contains("(x: Int)"))
68+
expect(textEdit.getRange().getStart.getLine == 3)
69+
expect(textEdit.getRange().getStart.getCharacter == 4)
70+
expect(textEdit.getRange().getEnd.getLine == 3)
71+
expect(textEdit.getRange().getEnd.getCharacter == 10)
72+
}
73+
}
74+
}
75+
}
76+
77+
}

0 commit comments

Comments
 (0)