From a7976022975b76cdb0d28b735d10774f3d6a3c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wro=C5=84ski?= Date: Wed, 3 Aug 2022 15:48:09 +0800 Subject: [PATCH 1/7] Fix counting end pos in using directives --- .../scala/build/tests/DirectiveTests.scala | 58 +++++++++++++++++++ .../scala/build/tests/SourcesTests.scala | 4 +- .../directives/DirectiveUtil.scala | 7 ++- .../cli/integration/BspTestDefinitions.scala | 6 +- 4 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala diff --git a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala new file mode 100644 index 0000000000..1e467d7d16 --- /dev/null +++ b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala @@ -0,0 +1,58 @@ +package scala.build.tests + +import com.eed3si9n.expecty.Expecty.expect + +import java.io.IOException +import scala.build.{BuildThreads, Directories, LocalRepo} +import scala.build.options.{BuildOptions, InternalOptions, MaybeScalaVersion} +import scala.build.tests.util.BloopServer +import build.Ops.EitherThrowOps +import scala.build.Position + +class DirectiveTests extends munit.FunSuite { + + val buildThreads = BuildThreads.create() + + def bloopConfigOpt = Some(BloopServer.bloopConfig) + + val extraRepoTmpDir = os.temp.dir(prefix = "scala-cli-tests-extra-repo-") + val directories = Directories.under(extraRepoTmpDir) + + override def afterAll(): Unit = { + TestInputs.tryRemoveAll(extraRepoTmpDir) + buildThreads.shutdown() + } + + val baseOptions = BuildOptions( + internal = InternalOptions( + localRepository = LocalRepo.localRepo(directories.localRepoDir), + keepDiagnostics = true + ) + ) + + test("resolving position of lib directive ") { + val testInputs = TestInputs( + os.rel / "simple.sc" -> + """//> using lib "com.lihaoyi::utest:0.7.10" + |""".stripMargin + ) + testInputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) { + (_, _, maybeBuild) => + val build = maybeBuild.orThrow + val dep = build.options.classPathOptions.extraDependencies.toSeq.headOption + assert(dep.nonEmpty) + + val position = dep.get.positions.headOption + assert(position.nonEmpty) + + val (startPos, endPos) = position.get match { + case Position.File(_, startPos, endPos) => (startPos, endPos) + case _ => sys.error("cannot happen") + } + + expect(startPos == (0,15)) + expect(endPos == (0,40)) + } + } + +} \ No newline at end of file diff --git a/modules/build/src/test/scala/scala/build/tests/SourcesTests.scala b/modules/build/src/test/scala/scala/build/tests/SourcesTests.scala index 6b32f05a30..a9a836d753 100644 --- a/modules/build/src/test/scala/scala/build/tests/SourcesTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/SourcesTests.scala @@ -477,9 +477,9 @@ class SourcesTests extends munit.FunSuite { expect( javaOpts(0).value.value == "-Dfoo1", - javaOpts(0).positions == Seq(Position.File(Right(root / "something.sc"), (0, 20), (0, 20))), + javaOpts(0).positions == Seq(Position.File(Right(root / "something.sc"), (0, 20), (0, 24))), javaOpts(1).value.value == "-Dfoo2=bar2", - javaOpts(1).positions == Seq(Position.File(Right(root / "something.sc"), (1, 20), (1, 20))) + javaOpts(1).positions == Seq(Position.File(Right(root / "something.sc"), (1, 20), (1, 29))) ) } } diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/DirectiveUtil.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/DirectiveUtil.scala index f1d793c1ce..0b73beadcb 100644 --- a/modules/directives/src/main/scala/scala/build/preprocessing/directives/DirectiveUtil.scala +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/DirectiveUtil.scala @@ -49,9 +49,10 @@ object DirectiveUtil { path: Either[String, os.Path], skipQuotes: Boolean = false ): Position.File = { - val line = v.getRelatedASTNode.getPosition.getLine - val column = v.getRelatedASTNode.getPosition.getColumn + (if (skipQuotes) 1 else 0) - Position.File(path, (line, column), (line, column)) + val line = v.getRelatedASTNode.getPosition.getLine + val column = v.getRelatedASTNode.getPosition.getColumn + (if (skipQuotes) 1 else 0) + val endLinePos = column + v.toString.length + Position.File(path, (line, column), (line, endLinePos)) } def scope(v: Value[_], cwd: ScopePath): Option[ScopePath] = diff --git a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala index fca9f1ead7..ce9eb4131d 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala @@ -521,7 +521,7 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) expectedStartLine = 0, expectedStartCharacter = 20, expectedEndLine = 0, - expectedEndCharacter = 20, + expectedEndCharacter = 31, strictlyCheckMessage = false ) } @@ -561,7 +561,7 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) expectedStartLine = 0, expectedStartCharacter = 15, expectedEndLine = 0, - expectedEndCharacter = 15, + expectedEndCharacter = 46, strictlyCheckMessage = false ) } @@ -1134,7 +1134,7 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) expectedStartLine = 0, expectedStartCharacter = 15, expectedEndLine = 0, - expectedEndCharacter = 15, + expectedEndCharacter = 26, strictlyCheckMessage = false ) } From 35a624cc4644723fd6bd972a69118ebf0f6981d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wro=C5=84ski?= Date: Wed, 3 Aug 2022 15:48:40 +0800 Subject: [PATCH 2/7] Use persistent logger to forward diagnostics to bsp --- .../main/scala/scala/build/bsp/BspImpl.scala | 2 +- .../scala/cli/commands/VerbosityOptions.scala | 4 +- .../main/scala/scala/cli/commands/Bsp.scala | 3 +- .../scala/scala/cli/commands/Compile.scala | 3 +- .../main/scala/scala/cli/commands/Doc.scala | 3 +- .../scala/scala/cli/commands/Metabrowse.scala | 3 +- .../scala/scala/cli/commands/Package.scala | 3 +- .../main/scala/scala/cli/commands/Repl.scala | 3 +- .../main/scala/scala/cli/commands/Run.scala | 3 +- .../main/scala/scala/cli/commands/Test.scala | 3 +- .../cli/integration/BspTestDefinitions.scala | 42 +++++++++++++++++++ 11 files changed, 62 insertions(+), 10 deletions(-) diff --git a/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala b/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala index 4ed0bbf5ad..2bd1381dd2 100644 --- a/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala +++ b/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala @@ -185,7 +185,7 @@ final class BspImpl( if (actionableDiagnostics.getOrElse(false)) { val projectOptions = options0Test.orElse(options0Main) - projectOptions.logActionableDiagnostics(logger) + projectOptions.logActionableDiagnostics(persistentLogger) } PreBuildProject(mainScope, testScope, persistentLogger.diagnostics) diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/VerbosityOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/VerbosityOptions.scala index a6b708bd18..b94479bec9 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/VerbosityOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/VerbosityOptions.scala @@ -12,7 +12,9 @@ final case class VerbosityOptions( verbose: Int @@ Counter = Tag.of(0), @HelpMessage("Interactive mode") @Name("i") - interactive: Option[Boolean] = None + interactive: Option[Boolean] = None, + @HelpMessage("Enable actionable diagnostics") + actions: Option[Boolean] = None ) { // format: on diff --git a/modules/cli/src/main/scala/scala/cli/commands/Bsp.scala b/modules/cli/src/main/scala/scala/cli/commands/Bsp.scala index aca2fde8b1..cc52849336 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Bsp.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Bsp.scala @@ -72,7 +72,8 @@ object Bsp extends ScalaCommand[BspOptions] { CurrentParams.workspaceOpt = Some(inputs.workspace) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = configDb.get(Keys.actions).getOrElse(None) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) BspThreads.withThreads { threads => val bsp = scala.build.bsp.Bsp.create( diff --git a/modules/cli/src/main/scala/scala/cli/commands/Compile.scala b/modules/cli/src/main/scala/scala/cli/commands/Compile.scala index 26af8ca36d..aa7fa59473 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Compile.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Compile.scala @@ -82,7 +82,8 @@ object Compile extends ScalaCommand[CompileOptions] { val compilerMaker = options.shared.compilerMaker(threads) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = configDb.get(Keys.actions).getOrElse(None) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) if (options.watch.watchMode) { val watcher = Build.watch( diff --git a/modules/cli/src/main/scala/scala/cli/commands/Doc.scala b/modules/cli/src/main/scala/scala/cli/commands/Doc.scala index 0577779e83..b130ad005f 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Doc.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Doc.scala @@ -36,7 +36,8 @@ object Doc extends ScalaCommand[DocOptions] { val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = configDb.get(Keys.actions).getOrElse(None) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) val builds = Build.build( diff --git a/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala b/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala index c0048cebce..f5ae42a2dc 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala @@ -59,7 +59,8 @@ object Metabrowse extends ScalaCommand[MetabrowseOptions] { val compilerMaker = options.shared.compilerMaker(threads) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = configDb.get(Keys.actions).getOrElse(None) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) val builds = Build.build( diff --git a/modules/cli/src/main/scala/scala/cli/commands/Package.scala b/modules/cli/src/main/scala/scala/cli/commands/Package.scala index 1efb2f1088..87f145d3af 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Package.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Package.scala @@ -66,7 +66,8 @@ object Package extends ScalaCommand[PackageOptions] { val cross = options.compileCross.cross.getOrElse(false) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = configDb.get(Keys.actions).getOrElse(None) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) if (options.watch.watchMode) { var expectedModifyEpochSecondOpt = Option.empty[Long] diff --git a/modules/cli/src/main/scala/scala/cli/commands/Repl.scala b/modules/cli/src/main/scala/scala/cli/commands/Repl.scala index e3097cdc78..87eb29ca1a 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Repl.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Repl.scala @@ -103,7 +103,8 @@ object Repl extends ScalaCommand[ReplOptions] { val cross = options.compileCross.cross.getOrElse(false) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = configDb.get(Keys.actions).getOrElse(None) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) if (inputs.isEmpty) { val artifacts = initialBuildOptions.artifacts(logger, Scope.Main).orExit(logger) diff --git a/modules/cli/src/main/scala/scala/cli/commands/Run.scala b/modules/cli/src/main/scala/scala/cli/commands/Run.scala index f1d4e7e595..877be8ad35 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Run.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Run.scala @@ -140,7 +140,8 @@ object Run extends ScalaCommand[RunOptions] { val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = configDb.get(Keys.actions).getOrElse(None) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) if (options.watch.watchMode) { var processOpt = Option.empty[(Process, CompletableFuture[_])] diff --git a/modules/cli/src/main/scala/scala/cli/commands/Test.scala b/modules/cli/src/main/scala/scala/cli/commands/Test.scala index f32474c7bd..e5828bc306 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Test.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Test.scala @@ -68,7 +68,8 @@ object Test extends ScalaCommand[TestOptions] { val cross = options.compileCross.cross.getOrElse(false) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = configDb.get(Keys.actions).getOrElse(None) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) def maybeTest(builds: Builds, allowExit: Boolean): Unit = { val optionsKeys = builds.map.keys.toVector.map(_.optionsKey).distinct diff --git a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala index ce9eb4131d..e351ccb974 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala @@ -1140,6 +1140,48 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) } } } + test("bsp should report actionable diagnostic when enabled") { + val inputs = TestInputs( + Seq( + os.rel / "Hello.scala" -> + s"""//> using lib "com.lihaoyi::os-lib:0.7.8" + | + |object Hello extends App { + | println("Hello") + |} + |""".stripMargin + ) + ) + withBsp(inputs, Seq(".", "--actions")) { + (root, localClient, remoteServer) => + async { + // prepare build + val buildTargetsResp = await(remoteServer.workspaceBuildTargets().asScala) + // build code + val targets = buildTargetsResp.getTargets.asScala.map(_.getId()).asJava + await(remoteServer.buildTargetCompile(new b.CompileParams(targets)).asScala) + + val visibleDiagnostics = + localClient.diagnostics().takeWhile(!_.getReset).flatMap(_.getDiagnostics.asScala) + + expect(visibleDiagnostics.nonEmpty) + expect(visibleDiagnostics.length == 1) + + val updateActionableDiagnostic = visibleDiagnostics.head + + checkDiagnostic( + diagnostic = updateActionableDiagnostic, + expectedMessage = "com.lihaoyi::os-lib:0.7.8 is outdated", + expectedSeverity = b.DiagnosticSeverity.WARNING, + expectedStartLine = 0, + expectedStartCharacter = 15, + expectedEndLine = 0, + expectedEndCharacter = 40, + strictlyCheckMessage = false + ) + } + } + } private def checkIfBloopProjectIsInitialised( root: os.Path, buildTargetsResp: b.WorkspaceBuildTargetsResult From 7fe2e10bf28ae9c10ae91aade6c0431956f768ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wro=C5=84ski?= Date: Wed, 3 Aug 2022 21:56:33 +0800 Subject: [PATCH 3/7] Use hint severity in ActionableDiagnostic --- .../scala/build/ConsoleBloopBuildClient.scala | 15 ++++++++++----- .../main/scala/scala/build/bsp/BspClient.scala | 6 +----- .../scala/scala/cli/internal/CliLogger.scala | 7 ++----- .../scala/scala/build/errors/Severity.scala | 17 ++++++++++++++--- .../cli/integration/BspTestDefinitions.scala | 4 ++-- .../build/actionable/ActionableDiagnostic.scala | 2 +- 6 files changed, 30 insertions(+), 21 deletions(-) diff --git a/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala b/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala index f9e0787334..94b02ad1b3 100644 --- a/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala +++ b/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala @@ -151,9 +151,14 @@ object ConsoleBloopBuildClient { private val red = Console.RED private val yellow = Console.YELLOW - def diagnosticPrefix(isError: Boolean): String = - if (isError) s"[${red}error$reset] " - else s"[${yellow}warn$reset] " + def diagnosticPrefix(severity: bsp4j.DiagnosticSeverity): String = + severity match { + case bsp4j.DiagnosticSeverity.ERROR => s"[${red}error$reset] " + case bsp4j.DiagnosticSeverity.WARNING => s"[${yellow}warn$reset] " + case bsp4j.DiagnosticSeverity.HINT => s"[${yellow}hint$reset] " + } + + def diagnosticPrefix(severity: Severity): String = diagnosticPrefix(severity.toBsp4j) def printFileDiagnostic( logger: Logger, @@ -163,7 +168,7 @@ object ConsoleBloopBuildClient { val isWarningOrError = diag.getSeverity == bsp4j.DiagnosticSeverity.ERROR || diag.getSeverity == bsp4j.DiagnosticSeverity.WARNING if (isWarningOrError) { - val prefix = diagnosticPrefix(diag.getSeverity == bsp4j.DiagnosticSeverity.ERROR) + val prefix = diagnosticPrefix(diag.getSeverity) val line = (diag.getRange.getStart.getLine + 1).toString + ":" val col = (diag.getRange.getStart.getCharacter + 1).toString + ":" @@ -215,7 +220,7 @@ object ConsoleBloopBuildClient { val isWarningOrError = true if (isWarningOrError) { val msgIt = message.linesIterator - val prefix = diagnosticPrefix(severity == Severity.Error) + val prefix = diagnosticPrefix(severity) logger.message(prefix + (if (msgIt.hasNext) " " + msgIt.next() else "")) msgIt.foreach(line => logger.message(prefix + line)) diff --git a/modules/build/src/main/scala/scala/build/bsp/BspClient.scala b/modules/build/src/main/scala/scala/build/bsp/BspClient.scala index 7bed76cfc6..02da2e14d4 100644 --- a/modules/build/src/main/scala/scala/build/bsp/BspClient.scala +++ b/modules/build/src/main/scala/scala/build/bsp/BspClient.scala @@ -194,11 +194,7 @@ class BspClient( val range = new b.Range(startPos, endPos) new b.Diagnostic(range, diag.message) } - val severity = diag.severity match { - case Severity.Error => b.DiagnosticSeverity.ERROR - case Severity.Warning => b.DiagnosticSeverity.WARNING - } - bDiag.setSeverity(severity) + bDiag.setSeverity(diag.severity.toBsp4j) val params = new b.PublishDiagnosticsParams( id, targetId, diff --git a/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala b/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala index 2e193e3ca8..70fb52c02c 100644 --- a/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala +++ b/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala @@ -58,7 +58,7 @@ class CliLogger( ) = if (positions.isEmpty) out.println( - s"${ConsoleBloopBuildClient.diagnosticPrefix(Severity.Error == severity)} $message" + s"${ConsoleBloopBuildClient.diagnosticPrefix(severity)} $message" ) else { val positions0 = positions.distinct @@ -75,10 +75,7 @@ class CliLogger( val endPos = new b.Position(f.endPos._1, f.endPos._2) val range = new b.Range(startPos, endPos) val diag = new b.Diagnostic(range, message) - diag.setSeverity(severity match { - case Severity.Error => b.DiagnosticSeverity.ERROR - case Severity.Warning => b.DiagnosticSeverity.WARNING - }) + diag.setSeverity(severity.toBsp4j) for (file <- f.path) { val lines = contentCache.getOrElseUpdate(file, os.read(file).linesIterator.toVector) diff --git a/modules/core/src/main/scala/scala/build/errors/Severity.scala b/modules/core/src/main/scala/scala/build/errors/Severity.scala index e409a8daf6..ff58cc0e8a 100644 --- a/modules/core/src/main/scala/scala/build/errors/Severity.scala +++ b/modules/core/src/main/scala/scala/build/errors/Severity.scala @@ -1,8 +1,19 @@ package scala.build.errors -sealed abstract class Severity extends Product with Serializable +import ch.epfl.scala.bsp4j as b +import ch.epfl.scala.bsp4j.DiagnosticSeverity +sealed abstract class Severity extends Product with Serializable { + def toBsp4j: b.DiagnosticSeverity +} object Severity { - case object Error extends Severity - case object Warning extends Severity + case object Error extends Severity { + override def toBsp4j: DiagnosticSeverity = b.DiagnosticSeverity.ERROR + } + case object Warning extends Severity { + override def toBsp4j: DiagnosticSeverity = b.DiagnosticSeverity.WARNING + } + case object Hint extends Severity { + override def toBsp4j: DiagnosticSeverity = b.DiagnosticSeverity.HINT + } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala index e351ccb974..7f20eb8229 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala @@ -1153,7 +1153,7 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) ) ) withBsp(inputs, Seq(".", "--actions")) { - (root, localClient, remoteServer) => + (_, localClient, remoteServer) => async { // prepare build val buildTargetsResp = await(remoteServer.workspaceBuildTargets().asScala) @@ -1172,7 +1172,7 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) checkDiagnostic( diagnostic = updateActionableDiagnostic, expectedMessage = "com.lihaoyi::os-lib:0.7.8 is outdated", - expectedSeverity = b.DiagnosticSeverity.WARNING, + expectedSeverity = b.DiagnosticSeverity.HINT, expectedStartLine = 0, expectedStartCharacter = 15, expectedEndLine = 0, diff --git a/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala b/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala index c8716813f1..6acb236ed3 100644 --- a/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala +++ b/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala @@ -19,7 +19,7 @@ abstract class ActionableDiagnostic { final def toDiagnostic: Diagnostic = Diagnostic( message = s"""|$message | To: $to""".stripMargin, - severity = Severity.Warning, + severity = Severity.Hint, positions = positions ) } From 4980e2d76e7fc9dba7ebe8eca00145aaf1205bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wro=C5=84ski?= Date: Wed, 3 Aug 2022 23:03:34 +0800 Subject: [PATCH 4/7] Set source as scala-cli for diagnostic generated in scala-cli --- modules/build/src/main/scala/scala/build/bsp/BspClient.scala | 1 + modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala | 1 + .../test/scala/scala/cli/integration/BspTestDefinitions.scala | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/modules/build/src/main/scala/scala/build/bsp/BspClient.scala b/modules/build/src/main/scala/scala/build/bsp/BspClient.scala index 02da2e14d4..c13d34e38c 100644 --- a/modules/build/src/main/scala/scala/build/bsp/BspClient.scala +++ b/modules/build/src/main/scala/scala/build/bsp/BspClient.scala @@ -195,6 +195,7 @@ class BspClient( new b.Diagnostic(range, diag.message) } bDiag.setSeverity(diag.severity.toBsp4j) + bDiag.setSource("scala-cli") val params = new b.PublishDiagnosticsParams( id, targetId, diff --git a/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala b/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala index 70fb52c02c..8d6ec1a738 100644 --- a/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala +++ b/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala @@ -76,6 +76,7 @@ class CliLogger( val range = new b.Range(startPos, endPos) val diag = new b.Diagnostic(range, message) diag.setSeverity(severity.toBsp4j) + diag.setSource("scala-cli") for (file <- f.path) { val lines = contentCache.getOrElseUpdate(file, os.read(file).linesIterator.toVector) diff --git a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala index 7f20eb8229..173d4544f3 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala @@ -1177,6 +1177,7 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) expectedStartCharacter = 15, expectedEndLine = 0, expectedEndCharacter = 40, + expectedSource = Some("scala-cli"), strictlyCheckMessage = false ) } @@ -1227,6 +1228,7 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) expectedStartCharacter: Int, expectedEndLine: Int, expectedEndCharacter: Int, + expectedSource: Option[String] = None, strictlyCheckMessage: Boolean = true ): Unit = { expect(diagnostic.getSeverity == expectedSeverity) @@ -1238,6 +1240,8 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) expect(diagnostic.getMessage == expectedMessage) else expect(diagnostic.getMessage.contains(expectedMessage)) + for(es <- expectedSource) + expect(diagnostic.getSource == es) } private def extractWorkspaceReloadResponse(workspaceReloadResult: AnyRef): Option[ResponseError] = From b343d913d577b258527a12ff4be1e2023c6a8cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wro=C5=84ski?= Date: Wed, 3 Aug 2022 23:10:31 +0800 Subject: [PATCH 5/7] Pass actionable diagnostics suggestion to related information in diagnostic --- .../scala/scala/build/bsp/BspClient.scala | 17 +++++++++----- .../scala/cli/commands/DependencyUpdate.scala | 6 ++--- .../scala/scala/cli/internal/CliLogger.scala | 19 +++++++++++++--- .../scala/scala/build/errors/Diagnostic.scala | 5 +++++ .../cli/integration/BspTestDefinitions.scala | 9 ++++++-- .../actionable/ActionableDiagnostic.scala | 22 ++++++++++--------- .../actionable/ActionablePreprocessor.scala | 6 ----- .../scala/build/options/BuildOptions.scala | 2 +- 8 files changed, 56 insertions(+), 30 deletions(-) diff --git a/modules/build/src/main/scala/scala/build/bsp/BspClient.scala b/modules/build/src/main/scala/scala/build/bsp/BspClient.scala index c13d34e38c..f5c6ed18dc 100644 --- a/modules/build/src/main/scala/scala/build/bsp/BspClient.scala +++ b/modules/build/src/main/scala/scala/build/bsp/BspClient.scala @@ -7,6 +7,8 @@ import java.net.URI import java.nio.file.Paths import java.util.concurrent.{ConcurrentHashMap, ExecutorService} +import ch.epfl.scala.bsp4j.Location + import scala.build.Position.File import scala.build.errors.{BuildException, CompositeBuildException, Diagnostic, Severity} import scala.build.postprocessing.LineConversion @@ -187,12 +189,17 @@ class BspClient( )(diag: Diagnostic): Seq[os.Path] = diag.positions.flatMap { case File(Right(path), (startLine, startC), (endL, endC)) => - val id = new b.TextDocumentIdentifier(path.toNIO.toUri.toASCIIString) - val bDiag = { - val startPos = new b.Position(startLine, startC) - val endPos = new b.Position(endL, endC) - val range = new b.Range(startPos, endPos) + val id = new b.TextDocumentIdentifier(path.toNIO.toUri.toASCIIString) + val startPos = new b.Position(startLine, startC) + val endPos = new b.Position(endL, endC) + val range = new b.Range(startPos, endPos) + val bDiag = new b.Diagnostic(range, diag.message) + + diag.relatedInformation.foreach { relatedInformation => + val location = new Location(path.toNIO.toUri.toASCIIString, range) + val related = new b.DiagnosticRelatedInformation(location, relatedInformation.message) + bDiag.setRelatedInformation(related) } bDiag.setSeverity(diag.severity.toBsp4j) bDiag.setSource("scala-cli") diff --git a/modules/cli/src/main/scala/scala/cli/commands/DependencyUpdate.scala b/modules/cli/src/main/scala/scala/cli/commands/DependencyUpdate.scala index bbd34426c9..f12bb4bd05 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/DependencyUpdate.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/DependencyUpdate.scala @@ -83,12 +83,12 @@ object DependencyUpdate extends ScalaCommand[DependencyUpdateOptions] { val appliedDiagnostics = updateDependencies(file, sortedByLine) os.write.over(file, appliedDiagnostics) diagnostics.foreach(diagnostic => - logger.message(s"Updated dependency to: ${diagnostic._2.to}") + logger.message(s"Updated dependency to: ${diagnostic._2.suggestion}") ) case (Left(file), diagnostics) => diagnostics.foreach { diagnostic => - logger.message(s"Warning: Scala CLI can't update ${diagnostic._2.to} in $file") + logger.message(s"Warning: Scala CLI can't update ${diagnostic._2.suggestion} in $file") } } } @@ -106,7 +106,7 @@ object DependencyUpdate extends ScalaCommand[DependencyUpdateOptions] { val startIndex = startIndicies(line) + column val endIndex = startIndex + diagnostic.oldDependency.render.length() - val newDependency = diagnostic.to + val newDependency = diagnostic.suggestion s"${fileContent.slice(0, startIndex)}$newDependency${fileContent.drop(endIndex)}" } } diff --git a/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala b/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala index 8d6ec1a738..a465d9ce15 100644 --- a/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala +++ b/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala @@ -1,6 +1,7 @@ package scala.cli.internal import ch.epfl.scala.{bsp4j => b} +import ch.epfl.scala.bsp4j.Location import coursier.cache.CacheLogger import coursier.cache.loggers.{FallbackRefreshDisplay, RefreshLogger} import org.scalajs.logging.{Level => ScalaJsLevel, Logger => ScalaJsLogger, ScalaConsoleLogger} @@ -9,6 +10,7 @@ import java.io.PrintStream import scala.build.blooprifle.BloopRifleLogger import scala.build.errors.{BuildException, CompositeBuildException, Diagnostic, Severity} +import scala.build.errors.Diagnostic.RelatedInformation import scala.build.internal.CustomProgressBarRefreshDisplay import scala.build.{ConsoleBloopBuildClient, Logger, Position} import scala.collection.mutable @@ -28,7 +30,8 @@ class CliLogger( d.positions, d.severity, d.message, - hashMap + hashMap, + d.relatedInformation ) } } @@ -54,7 +57,8 @@ class CliLogger( positions: Seq[Position], severity: Severity, message: String, - contentCache: mutable.Map[os.Path, Seq[String]] + contentCache: mutable.Map[os.Path, Seq[String]], + relatedInformation: Option[RelatedInformation] ) = if (positions.isEmpty) out.println( @@ -78,6 +82,15 @@ class CliLogger( diag.setSeverity(severity.toBsp4j) diag.setSource("scala-cli") + for { + filePath <- f.path + info <- relatedInformation + } { + val location = new Location(filePath.toNIO.toUri.toASCIIString, range) + val related = new b.DiagnosticRelatedInformation(location, info.message) + diag.setRelatedInformation(related) + } + for (file <- f.path) { val lines = contentCache.getOrElseUpdate(file, os.read(file).linesIterator.toVector) if (f.startPos._1 < lines.length) @@ -110,7 +123,7 @@ class CliLogger( for (ex <- c.exceptions) printEx(ex, contentCache) case _ => - printDiagnostic(ex.positions, Severity.Error, ex.getMessage(), contentCache) + printDiagnostic(ex.positions, Severity.Error, ex.getMessage(), contentCache, None) } def log(ex: BuildException): Unit = diff --git a/modules/core/src/main/scala/scala/build/errors/Diagnostic.scala b/modules/core/src/main/scala/scala/build/errors/Diagnostic.scala index 7ad338e199..6ee430aac8 100644 --- a/modules/core/src/main/scala/scala/build/errors/Diagnostic.scala +++ b/modules/core/src/main/scala/scala/build/errors/Diagnostic.scala @@ -1,14 +1,19 @@ package scala.build.errors import scala.build.Position +import scala.build.errors.Diagnostic.RelatedInformation + trait Diagnostic { def message: String def severity: Severity def positions: Seq[Position] + def relatedInformation: Option[RelatedInformation] = None } object Diagnostic { + + case class RelatedInformation(message: String) object Messages { val bloopTooOld = "JVM that is hosting bloop is older than the requested runtime. Please run `scala-cli bloop exit`, and then use `--jvm` flag to restart Bloop" diff --git a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala index 173d4544f3..caae873cd7 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala @@ -1141,9 +1141,10 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) } } test("bsp should report actionable diagnostic when enabled") { + val fileName = "Hello.scala" val inputs = TestInputs( Seq( - os.rel / "Hello.scala" -> + os.rel / fileName -> s"""//> using lib "com.lihaoyi::os-lib:0.7.8" | |object Hello extends App { @@ -1153,7 +1154,7 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) ) ) withBsp(inputs, Seq(".", "--actions")) { - (_, localClient, remoteServer) => + (root, localClient, remoteServer) => async { // prepare build val buildTargetsResp = await(remoteServer.workspaceBuildTargets().asScala) @@ -1180,6 +1181,10 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) expectedSource = Some("scala-cli"), strictlyCheckMessage = false ) + + val relatedInformation = updateActionableDiagnostic.getRelatedInformation() + expect(relatedInformation.getMessage.contains("com.lihaoyi::os-lib:")) + expect(relatedInformation.getLocation().getUri() == (root /fileName).toNIO.toUri.toASCIIString) } } } diff --git a/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala b/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala index 6acb236ed3..faa09629d1 100644 --- a/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala +++ b/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala @@ -4,8 +4,10 @@ import dependency._ import scala.build.Position import scala.build.errors.{Diagnostic, Severity} +import scala.build.errors.Diagnostic.RelatedInformation -abstract class ActionableDiagnostic { + +abstract class ActionableDiagnostic extends Diagnostic { /** Provide the message of actionable diagnostic */ @@ -13,26 +15,26 @@ abstract class ActionableDiagnostic { /** Provide the new content which will be replaced by actionable diagnostic */ - def to: String + def suggestion: String + def positions: Seq[Position] - final def toDiagnostic: Diagnostic = Diagnostic( - message = s"""|$message - | To: $to""".stripMargin, - severity = Severity.Hint, - positions = positions - ) + override def severity = Severity.Hint + + override def relatedInformation: Option[RelatedInformation] = Some(RelatedInformation(suggestion)) } object ActionableDiagnostic { case class ActionableDependencyUpdateDiagnostic( - message: String, + msg: String, positions: Seq[Position], oldDependency: AnyDependency, newVersion: String ) extends ActionableDiagnostic { - override def to: String = oldDependency.copy(version = newVersion).render + override def message: String = s"""|$msg + | ${oldDependency.render} -> $suggestion""".stripMargin + override def suggestion: String = oldDependency.copy(version = newVersion).render } } diff --git a/modules/options/src/main/scala/scala/build/actionable/ActionablePreprocessor.scala b/modules/options/src/main/scala/scala/build/actionable/ActionablePreprocessor.scala index 4e34a5065c..c88b873f5a 100644 --- a/modules/options/src/main/scala/scala/build/actionable/ActionablePreprocessor.scala +++ b/modules/options/src/main/scala/scala/build/actionable/ActionablePreprocessor.scala @@ -19,10 +19,4 @@ object ActionablePreprocessor { .left.map(CompositeBuildException(_)) .map(_.flatten) - def generateDiagnostics( - options: BuildOptions - ): Either[BuildException, Seq[Diagnostic]] = - generateActionableDiagnostics(options) - .map(_.map(_.toDiagnostic)) - } diff --git a/modules/options/src/main/scala/scala/build/options/BuildOptions.scala b/modules/options/src/main/scala/scala/build/options/BuildOptions.scala index 047ff72ad0..b452c6df19 100644 --- a/modules/options/src/main/scala/scala/build/options/BuildOptions.scala +++ b/modules/options/src/main/scala/scala/build/options/BuildOptions.scala @@ -590,7 +590,7 @@ final case class BuildOptions( def validate: Seq[Diagnostic] = BuildOptionsRule.validateAll(this) def logActionableDiagnostics(logger: Logger): Unit = { - val actionableDiagnostics = ActionablePreprocessor.generateDiagnostics(this) + val actionableDiagnostics = ActionablePreprocessor.generateActionableDiagnostics(this) actionableDiagnostics match { case Left(e) => logger.debug(e) From 6311689c66ad5bb9dae754379437a5a749092155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wro=C5=84ski?= Date: Wed, 3 Aug 2022 23:15:49 +0800 Subject: [PATCH 6/7] Fix printing hint diagnostic in command line --- .../main/scala/scala/build/ConsoleBloopBuildClient.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala b/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala index 94b02ad1b3..57a9bf7d45 100644 --- a/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala +++ b/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala @@ -163,11 +163,12 @@ object ConsoleBloopBuildClient { def printFileDiagnostic( logger: Logger, path: Either[String, os.Path], - diag: bsp4j.Diagnostic + diag: bsp4j.Diagnostic, ): Unit = { - val isWarningOrError = diag.getSeverity == bsp4j.DiagnosticSeverity.ERROR || - diag.getSeverity == bsp4j.DiagnosticSeverity.WARNING - if (isWarningOrError) { + val isWarningOrErrorOrHint = diag.getSeverity == bsp4j.DiagnosticSeverity.ERROR || + diag.getSeverity == bsp4j.DiagnosticSeverity.WARNING || + diag.getSeverity == bsp4j.DiagnosticSeverity.HINT + if (isWarningOrErrorOrHint) { val prefix = diagnosticPrefix(diag.getSeverity) val line = (diag.getRange.getStart.getLine + 1).toString + ":" From 71954ef315aa22e43c1a00afae194e13a5c91c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wro=C5=84ski?= Date: Wed, 3 Aug 2022 23:23:11 +0800 Subject: [PATCH 7/7] Fix scalafmt --- .../scala/build/ConsoleBloopBuildClient.scala | 2 +- .../scala/build/tests/DirectiveTests.scala | 14 +++++------ .../main/scala/scala/cli/commands/Bsp.scala | 6 +++-- .../scala/scala/cli/commands/Compile.scala | 6 +++-- .../main/scala/scala/cli/commands/Doc.scala | 6 +++-- .../scala/scala/cli/commands/Metabrowse.scala | 6 +++-- .../scala/scala/cli/commands/Package.scala | 6 +++-- .../main/scala/scala/cli/commands/Repl.scala | 6 +++-- .../main/scala/scala/cli/commands/Run.scala | 6 +++-- .../main/scala/scala/cli/commands/Test.scala | 6 +++-- .../scala/scala/cli/internal/CliLogger.scala | 2 +- .../scala/scala/build/errors/Diagnostic.scala | 1 - .../scala/scala/build/errors/Severity.scala | 8 +++---- .../cli/integration/BspTestDefinitions.scala | 24 +++++++++---------- .../actionable/ActionableDiagnostic.scala | 1 - website/docs/reference/cli-options.md | 4 ++++ 16 files changed, 61 insertions(+), 43 deletions(-) diff --git a/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala b/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala index 57a9bf7d45..d2b0ad4280 100644 --- a/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala +++ b/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala @@ -163,7 +163,7 @@ object ConsoleBloopBuildClient { def printFileDiagnostic( logger: Logger, path: Either[String, os.Path], - diag: bsp4j.Diagnostic, + diag: bsp4j.Diagnostic ): Unit = { val isWarningOrErrorOrHint = diag.getSeverity == bsp4j.DiagnosticSeverity.ERROR || diag.getSeverity == bsp4j.DiagnosticSeverity.WARNING || diff --git a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala index 1e467d7d16..c140e179dc 100644 --- a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala @@ -16,7 +16,7 @@ class DirectiveTests extends munit.FunSuite { def bloopConfigOpt = Some(BloopServer.bloopConfig) val extraRepoTmpDir = os.temp.dir(prefix = "scala-cli-tests-extra-repo-") - val directories = Directories.under(extraRepoTmpDir) + val directories = Directories.under(extraRepoTmpDir) override def afterAll(): Unit = { TestInputs.tryRemoveAll(extraRepoTmpDir) @@ -38,8 +38,8 @@ class DirectiveTests extends munit.FunSuite { ) testInputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) { (_, _, maybeBuild) => - val build = maybeBuild.orThrow - val dep = build.options.classPathOptions.extraDependencies.toSeq.headOption + val build = maybeBuild.orThrow + val dep = build.options.classPathOptions.extraDependencies.toSeq.headOption assert(dep.nonEmpty) val position = dep.get.positions.headOption @@ -47,12 +47,12 @@ class DirectiveTests extends munit.FunSuite { val (startPos, endPos) = position.get match { case Position.File(_, startPos, endPos) => (startPos, endPos) - case _ => sys.error("cannot happen") + case _ => sys.error("cannot happen") } - expect(startPos == (0,15)) - expect(endPos == (0,40)) + expect(startPos == (0, 15)) + expect(endPos == (0, 40)) } } -} \ No newline at end of file +} diff --git a/modules/cli/src/main/scala/scala/cli/commands/Bsp.scala b/modules/cli/src/main/scala/scala/cli/commands/Bsp.scala index cc52849336..bc1b3d7251 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Bsp.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Bsp.scala @@ -72,8 +72,10 @@ object Bsp extends ScalaCommand[BspOptions] { CurrentParams.workspaceOpt = Some(inputs.workspace) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = - options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse( + configDb.get(Keys.actions).getOrElse(None) + ) BspThreads.withThreads { threads => val bsp = scala.build.bsp.Bsp.create( diff --git a/modules/cli/src/main/scala/scala/cli/commands/Compile.scala b/modules/cli/src/main/scala/scala/cli/commands/Compile.scala index aa7fa59473..f4829aa5ba 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Compile.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Compile.scala @@ -82,8 +82,10 @@ object Compile extends ScalaCommand[CompileOptions] { val compilerMaker = options.shared.compilerMaker(threads) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = - options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse( + configDb.get(Keys.actions).getOrElse(None) + ) if (options.watch.watchMode) { val watcher = Build.watch( diff --git a/modules/cli/src/main/scala/scala/cli/commands/Doc.scala b/modules/cli/src/main/scala/scala/cli/commands/Doc.scala index b130ad005f..fb59962e5d 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Doc.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Doc.scala @@ -36,8 +36,10 @@ object Doc extends ScalaCommand[DocOptions] { val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = - options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse( + configDb.get(Keys.actions).getOrElse(None) + ) val builds = Build.build( diff --git a/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala b/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala index f5ae42a2dc..dc74d9b1a6 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala @@ -59,8 +59,10 @@ object Metabrowse extends ScalaCommand[MetabrowseOptions] { val compilerMaker = options.shared.compilerMaker(threads) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = - options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse( + configDb.get(Keys.actions).getOrElse(None) + ) val builds = Build.build( diff --git a/modules/cli/src/main/scala/scala/cli/commands/Package.scala b/modules/cli/src/main/scala/scala/cli/commands/Package.scala index 87f145d3af..b3ca133262 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Package.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Package.scala @@ -66,8 +66,10 @@ object Package extends ScalaCommand[PackageOptions] { val cross = options.compileCross.cross.getOrElse(false) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = - options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse( + configDb.get(Keys.actions).getOrElse(None) + ) if (options.watch.watchMode) { var expectedModifyEpochSecondOpt = Option.empty[Long] diff --git a/modules/cli/src/main/scala/scala/cli/commands/Repl.scala b/modules/cli/src/main/scala/scala/cli/commands/Repl.scala index 87eb29ca1a..f652a13616 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Repl.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Repl.scala @@ -103,8 +103,10 @@ object Repl extends ScalaCommand[ReplOptions] { val cross = options.compileCross.cross.getOrElse(false) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = - options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse( + configDb.get(Keys.actions).getOrElse(None) + ) if (inputs.isEmpty) { val artifacts = initialBuildOptions.artifacts(logger, Scope.Main).orExit(logger) diff --git a/modules/cli/src/main/scala/scala/cli/commands/Run.scala b/modules/cli/src/main/scala/scala/cli/commands/Run.scala index 877be8ad35..2f4ce17157 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Run.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Run.scala @@ -140,8 +140,10 @@ object Run extends ScalaCommand[RunOptions] { val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = - options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse( + configDb.get(Keys.actions).getOrElse(None) + ) if (options.watch.watchMode) { var processOpt = Option.empty[(Process, CompletableFuture[_])] diff --git a/modules/cli/src/main/scala/scala/cli/commands/Test.scala b/modules/cli/src/main/scala/scala/cli/commands/Test.scala index e5828bc306..21cd6c756a 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Test.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Test.scala @@ -68,8 +68,10 @@ object Test extends ScalaCommand[TestOptions] { val cross = options.compileCross.cross.getOrElse(false) val configDb = ConfigDb.open(options.shared.directories.directories) .orExit(logger) - val actionableDiagnostics = - options.shared.logging.verbosityOptions.actions.orElse(configDb.get(Keys.actions).getOrElse(None)) + val actionableDiagnostics = + options.shared.logging.verbosityOptions.actions.orElse( + configDb.get(Keys.actions).getOrElse(None) + ) def maybeTest(builds: Builds, allowExit: Boolean): Unit = { val optionsKeys = builds.map.keys.toVector.map(_.optionsKey).distinct diff --git a/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala b/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala index a465d9ce15..99f9264b5c 100644 --- a/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala +++ b/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala @@ -84,7 +84,7 @@ class CliLogger( for { filePath <- f.path - info <- relatedInformation + info <- relatedInformation } { val location = new Location(filePath.toNIO.toUri.toASCIIString, range) val related = new b.DiagnosticRelatedInformation(location, info.message) diff --git a/modules/core/src/main/scala/scala/build/errors/Diagnostic.scala b/modules/core/src/main/scala/scala/build/errors/Diagnostic.scala index 6ee430aac8..0381325b33 100644 --- a/modules/core/src/main/scala/scala/build/errors/Diagnostic.scala +++ b/modules/core/src/main/scala/scala/build/errors/Diagnostic.scala @@ -3,7 +3,6 @@ package scala.build.errors import scala.build.Position import scala.build.errors.Diagnostic.RelatedInformation - trait Diagnostic { def message: String def severity: Severity diff --git a/modules/core/src/main/scala/scala/build/errors/Severity.scala b/modules/core/src/main/scala/scala/build/errors/Severity.scala index ff58cc0e8a..9d491f6380 100644 --- a/modules/core/src/main/scala/scala/build/errors/Severity.scala +++ b/modules/core/src/main/scala/scala/build/errors/Severity.scala @@ -1,19 +1,19 @@ package scala.build.errors import ch.epfl.scala.bsp4j as b -import ch.epfl.scala.bsp4j.DiagnosticSeverity + sealed abstract class Severity extends Product with Serializable { def toBsp4j: b.DiagnosticSeverity } object Severity { case object Error extends Severity { - override def toBsp4j: DiagnosticSeverity = b.DiagnosticSeverity.ERROR + override def toBsp4j: b.DiagnosticSeverity = b.DiagnosticSeverity.ERROR } case object Warning extends Severity { - override def toBsp4j: DiagnosticSeverity = b.DiagnosticSeverity.WARNING + override def toBsp4j: b.DiagnosticSeverity = b.DiagnosticSeverity.WARNING } case object Hint extends Severity { - override def toBsp4j: DiagnosticSeverity = b.DiagnosticSeverity.HINT + override def toBsp4j: b.DiagnosticSeverity = b.DiagnosticSeverity.HINT } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala index caae873cd7..8f6e2581e5 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala @@ -1143,15 +1143,13 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) test("bsp should report actionable diagnostic when enabled") { val fileName = "Hello.scala" val inputs = TestInputs( - Seq( - os.rel / fileName -> - s"""//> using lib "com.lihaoyi::os-lib:0.7.8" - | - |object Hello extends App { - | println("Hello") - |} - |""".stripMargin - ) + os.rel / fileName -> + s"""//> using lib "com.lihaoyi::os-lib:0.7.8" + | + |object Hello extends App { + | println("Hello") + |} + |""".stripMargin ) withBsp(inputs, Seq(".", "--actions")) { (root, localClient, remoteServer) => @@ -1169,7 +1167,7 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) expect(visibleDiagnostics.length == 1) val updateActionableDiagnostic = visibleDiagnostics.head - + checkDiagnostic( diagnostic = updateActionableDiagnostic, expectedMessage = "com.lihaoyi::os-lib:0.7.8 is outdated", @@ -1184,7 +1182,9 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) val relatedInformation = updateActionableDiagnostic.getRelatedInformation() expect(relatedInformation.getMessage.contains("com.lihaoyi::os-lib:")) - expect(relatedInformation.getLocation().getUri() == (root /fileName).toNIO.toUri.toASCIIString) + expect( + relatedInformation.getLocation().getUri() == (root / fileName).toNIO.toUri.toASCIIString + ) } } } @@ -1245,7 +1245,7 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) expect(diagnostic.getMessage == expectedMessage) else expect(diagnostic.getMessage.contains(expectedMessage)) - for(es <- expectedSource) + for (es <- expectedSource) expect(diagnostic.getSource == es) } diff --git a/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala b/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala index faa09629d1..f14a31d440 100644 --- a/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala +++ b/modules/options/src/main/scala/scala/build/actionable/ActionableDiagnostic.scala @@ -6,7 +6,6 @@ import scala.build.Position import scala.build.errors.{Diagnostic, Severity} import scala.build.errors.Diagnostic.RelatedInformation - abstract class ActionableDiagnostic extends Diagnostic { /** Provide the message of actionable diagnostic diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index 7f175cee7e..1dfcc6ee3c 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -1957,6 +1957,10 @@ Aliases: `-i` Interactive mode +#### `--actions` + +Enable actionable diagnostics + ## Watch options Available in commands: