diff options
Diffstat (limited to 'contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala')
-rw-r--r-- | contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala b/contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala new file mode 100644 index 00000000..d1b9a66b --- /dev/null +++ b/contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala @@ -0,0 +1,138 @@ +package mill.contrib.bsp + +import java.io.File +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicInteger + +import ch.epfl.scala.bsp4j._ +import ch.epfl.scala.{bsp4j => bsp} +import mill.api.{BuildProblemReporter, Problem} + +import scala.collection.JavaConverters._ +import scala.collection.concurrent +import scala.language.implicitConversions + +/** + * Specialized reporter that sends compilation diagnostics + * for each problem it logs, either as information, warning or + * error as well as task finish notifications of type `compile-report`. + * + * @param client the client to send diagnostics to + * @param targetId the target id of the target whose compilation + * the diagnostics are related to + * @param taskId a unique id of the compilation task of the target + * specified by `targetId` + * @param compilationOriginId optional origin id the client assigned to + * the compilation request. Needs to be sent + * back as part of the published diagnostics + * as well as compile report + */ +class BspLoggedReporter(client: bsp.BuildClient, + targetId: BuildTargetIdentifier, + taskId: TaskId, + compilationOriginId: Option[String]) extends BuildProblemReporter { + + var errors = new AtomicInteger(0) + var warnings = new AtomicInteger(0) + var infos = new AtomicInteger(0) + var diagnosticMap: concurrent.Map[TextDocumentIdentifier, bsp.PublishDiagnosticsParams] = + new ConcurrentHashMap[TextDocumentIdentifier, bsp.PublishDiagnosticsParams]().asScala + + override def logError(problem: Problem): Unit = { + client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId)) + errors.incrementAndGet() + } + + override def logInfo(problem: Problem): Unit = { + client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId)) + infos.incrementAndGet() + } + + // Obtains the parameters for sending diagnostics about the given Problem ( as well as + // about all previous problems generated for the same text file ) related to the specified + // targetId, incorporating the given optional originId ( generated by the client for the + // compile request ) + //TODO: document that if the problem is a general information without a text document + // associated to it, then the document field of the diagnostic is set to the uri of the target + private[this] def getDiagnostics(problem: Problem, targetId: bsp.BuildTargetIdentifier, originId: Option[String]): + bsp.PublishDiagnosticsParams = { + val diagnostic = getSingleDiagnostic(problem) + val sourceFile = problem.position.sourceFile + val textDocument = new TextDocumentIdentifier( + sourceFile.getOrElse(None) match { + case None => targetId.getUri + case f: File => f.toURI.toString + }) + val params = new bsp.PublishDiagnosticsParams(textDocument, + targetId, + appendDiagnostics(textDocument, + diagnostic).asJava, + true) + + if (originId.nonEmpty) { + params.setOriginId(originId.get) + } + diagnosticMap.put(textDocument, params) + params + } + + // Update the published diagnostics for the fiven text file by + // adding the recently computed diagnostic to the list of + // all previous diagnostics generated for the same file. + private[this] def appendDiagnostics(textDocument: TextDocumentIdentifier, + currentDiagnostic: Diagnostic): List[Diagnostic] = { + diagnosticMap.putIfAbsent(textDocument, new bsp.PublishDiagnosticsParams( + textDocument, + targetId, + List.empty[Diagnostic].asJava, true)) + diagnosticMap(textDocument).getDiagnostics.asScala.toList ++ List(currentDiagnostic) + } + + // Computes the diagnostic related to the given Problem + private[this] def getSingleDiagnostic(problem: Problem): Diagnostic = { + val pos = problem.position + val i: Integer = pos.startLine.orElse(pos.line).getOrElse[Int](0) + println(i) + val start = new bsp.Position( + pos.startLine.orElse(pos.line).getOrElse[Int](0), + pos.startOffset.orElse(pos.offset).getOrElse[Int](0) + ) + val end = new bsp.Position( + pos.endLine.orElse(pos.line).getOrElse[Int](start.getLine.intValue()), + pos.endOffset.orElse(pos.offset).getOrElse[Int](start.getCharacter.intValue())) + val diagnostic = new bsp.Diagnostic(new bsp.Range(start, end), problem.message) + diagnostic.setCode(pos.lineContent) + diagnostic.setSource("compiler from mill") + diagnostic.setSeverity(problem.severity match { + case mill.api.Info => bsp.DiagnosticSeverity.INFORMATION + case mill.api.Error => bsp.DiagnosticSeverity.ERROR + case mill.api.Warn => bsp.DiagnosticSeverity.WARNING + } + ) + diagnostic + } + + override def logWarning(problem: Problem): Unit = { + client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId)) + warnings.incrementAndGet() + } + + override def printSummary(): Unit = { + val taskFinishParams = new TaskFinishParams(taskId, getStatusCode) + taskFinishParams.setEventTime(System.currentTimeMillis()) + taskFinishParams.setMessage("Finished compiling target: " + targetId.getUri) + taskFinishParams.setDataKind("compile-report") + val compileReport = new CompileReport(targetId, errors.get, warnings.get) + compilationOriginId match { + case Some(id) => compileReport.setOriginId(id) + case None => + } + taskFinishParams.setData(compileReport) + client.onBuildTaskFinish(taskFinishParams) + } + + // Compute the compilation status code + private[this] def getStatusCode: StatusCode = { + if (errors.get > 0) StatusCode.ERROR else StatusCode.OK + } +} |