summaryrefslogtreecommitdiff
path: root/contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala
blob: b4f0260df62682b261ac2adc63e3df90f7c87156 (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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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.{Info, Problem, Warn, BuildProblemReporter}

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

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

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

  // Compute the compilation status code
  private[this] def getStatusCode: StatusCode = {
    if (errors.get > 0) StatusCode.ERROR else StatusCode.OK
  }

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