summaryrefslogtreecommitdiff
path: root/contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala
blob: 06c45b791ffde4f6e7b00ec9949294667b9a34d6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package mill.contrib.bsp

import java.io.File
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.{ConcurrentHashMap, ConcurrentMap}

import ch.epfl.scala.bsp4j._
import ch.epfl.scala.{bsp4j => bsp}
import sbt.internal.inc.ManagedLoggedReporter
import sbt.internal.util.ManagedLogger
import xsbti.{Problem, Severity}

import scala.collection.JavaConverters._
import scala.collection.concurrent
import scala.compat.java8.OptionConverters._
import scala.io.Source

class BspLoggedReporter(client: bsp.BuildClient,
                        targetId: BuildTargetIdentifier,
                        taskId: TaskId,
                        compilationOriginId: Option[String],
                        maxErrors: Int,
                        logger: ManagedLogger) extends ManagedLoggedReporter(maxErrors, logger) {

  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()
    super.logError(problem)
  }

  override def logInfo(problem: Problem): Unit = {
    client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId))
    infos.incrementAndGet()
    super.logInfo(problem)
  }

  override def logWarning(problem: Problem): Unit = {
    client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId))
    warnings.incrementAndGet()
    super.logWarning(problem)
  }

  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)
  }

  //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
  def getDiagnostics(problem: Problem, targetId: bsp.BuildTargetIdentifier, originId: Option[String]):
                                                                                bsp.PublishDiagnosticsParams = {
      val diagnostic = getSingleDiagnostic(problem)
      val sourceFile = problem.position().sourceFile().asScala
      val textDocument = new TextDocumentIdentifier(
        sourceFile.getOrElse(None) match {
        case None => targetId.getUri
        case f: File => f.toPath.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
    }

  private[this] def getStatusCode: StatusCode = {
    if (errors.get > 0) StatusCode.ERROR else StatusCode.OK
  }

  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)
  }

  private[this] def getSingleDiagnostic(problem: Problem): Diagnostic ={

    val start = new bsp.Position(
      problem.position.startLine.asScala.getOrElse(problem.position.line.asScala.getOrElse(0)),
      problem.position.startOffset.asScala.getOrElse(problem.position.offset.asScala.getOrElse(0)))
    val end = new bsp.Position(
      problem.position.endLine.asScala.getOrElse(problem.position.line.asScala.getOrElse(0)),
      problem.position.endOffset.asScala.getOrElse(problem.position.offset.asScala.getOrElse(0)))
    val diagnostic = new bsp.Diagnostic(new bsp.Range(start, end), problem.message)
    diagnostic.setCode(problem.position.lineContent)
    diagnostic.setSource("compiler from mill")
    diagnostic.setSeverity( problem.severity match  {
      case Severity.Info => bsp.DiagnosticSeverity.INFORMATION
      case Severity.Error => bsp.DiagnosticSeverity.ERROR
      case Severity.Warn => bsp.DiagnosticSeverity.WARNING
    }
    )
    diagnostic
  }
}