Add Problem#rendered to customize how problems are shown

Dotty has its own logic for displaying problems with the proper file
path, position, and caret, but if we store this information in
Problem#message we end up with duplicated information in the output
since Zinc will prepend/append similar things (see
sbt.internal.inc.ProblemStringFormats). So far, we worked around this in
Dotty by using an empty position in the sbt bridge reporter, but this
means that crucial semantic information that could be used by a Build
Server Protocol implementation and other tools is lost. This commit
allows us to avoid by adding an optional `rendered` field to `Problem`:
when this field is set, its value controls what the user sees, otherwise
we fallback to the default behavior (the logic to do this will be added to
Zinc after this PR is merged and a new release of sbt-util is made).
This commit is contained in:
Guillaume Martres 2018-08-28 02:03:47 +09:00
parent e905b44a33
commit 15522a0cbe
6 changed files with 42 additions and 10 deletions

View File

@ -124,8 +124,9 @@ lazy val utilLogging = (project in internalPath / "util-logging")
exclude[DirectMissingMethodProblem]("sbt.internal.util.SuccessEvent.copy*"), exclude[DirectMissingMethodProblem]("sbt.internal.util.SuccessEvent.copy*"),
exclude[DirectMissingMethodProblem]("sbt.internal.util.TraceEvent.copy*"), exclude[DirectMissingMethodProblem]("sbt.internal.util.TraceEvent.copy*"),
exclude[DirectMissingMethodProblem]("sbt.internal.util.StringEvent.copy*"), exclude[DirectMissingMethodProblem]("sbt.internal.util.StringEvent.copy*"),
// Private final class constructor changed // Private final class constructors changed
exclude[DirectMissingMethodProblem]("sbt.util.InterfaceUtil#ConcretePosition.this"), exclude[DirectMissingMethodProblem]("sbt.util.InterfaceUtil#ConcretePosition.this"),
exclude[DirectMissingMethodProblem]("sbt.util.InterfaceUtil#ConcreteProblem.this"),
), ),
) )
.configure(addSbtIO) .configure(addSbtIO)

View File

@ -3,10 +3,20 @@
*/ */
package xsbti; package xsbti;
import java.util.Optional;
public interface Problem public interface Problem
{ {
String category(); String category();
Severity severity(); Severity severity();
String message(); String message();
Position position(); Position position();
// Default value to avoid breaking binary compatibility
/**
* If present, the string shown to the user when displaying this Problem.
* Otherwise, the Problem will be shown in an implementation-defined way
* based on the values of its other fields.
*/
default Optional<String> rendered() { return Optional.empty(); }
} }

View File

@ -29,4 +29,5 @@ type Problem {
severity: Severity! severity: Severity!
message: String! message: String!
position: Position! position: Position!
rendered: String
} }

View File

@ -4,8 +4,8 @@
package sbt.internal.util.codec package sbt.internal.util.codec
import xsbti.{ Problem, Severity, Position } import xsbti.{ Problem, Severity, Position }
import sbt.util.InterfaceUtil.problem
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder } import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
import java.util.Optional
trait ProblemFormats { self: SeverityFormats with PositionFormats with sjsonnew.BasicJsonProtocol => trait ProblemFormats { self: SeverityFormats with PositionFormats with sjsonnew.BasicJsonProtocol =>
implicit lazy val ProblemFormat: JsonFormat[Problem] = new JsonFormat[Problem] { implicit lazy val ProblemFormat: JsonFormat[Problem] = new JsonFormat[Problem] {
@ -13,12 +13,20 @@ trait ProblemFormats { self: SeverityFormats with PositionFormats with sjsonnew.
jsOpt match { jsOpt match {
case Some(js) => case Some(js) =>
unbuilder.beginObject(js) unbuilder.beginObject(js)
val category = unbuilder.readField[String]("category") val category0 = unbuilder.readField[String]("category")
val severity = unbuilder.readField[Severity]("severity") val severity0 = unbuilder.readField[Severity]("severity")
val message = unbuilder.readField[String]("message") val message0 = unbuilder.readField[String]("message")
val position = unbuilder.readField[Position]("position") val position0 = unbuilder.readField[Position]("position")
val rendered0 = unbuilder.readField[Optional[String]]("rendered")
unbuilder.endObject() unbuilder.endObject()
problem(category, position, message, severity) new Problem {
override val category = category0
override val position = position0
override val message = message0
override val severity = severity0
override val rendered = rendered0
}
case None => case None =>
deserializationError("Expected JsObject but found None") deserializationError("Expected JsObject but found None")
} }
@ -29,6 +37,7 @@ trait ProblemFormats { self: SeverityFormats with PositionFormats with sjsonnew.
builder.addField("severity", obj.severity) builder.addField("severity", obj.severity)
builder.addField("message", obj.message) builder.addField("message", obj.message)
builder.addField("position", obj.position) builder.addField("position", obj.position)
builder.addField("rendered", obj.rendered)
builder.endObject() builder.endObject()
} }
} }

View File

@ -89,8 +89,16 @@ object InterfaceUtil {
endLine0, endLine0,
endColumn0) endColumn0)
@deprecated("Use the overload of this method with more arguments", "1.2.2")
def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem = def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem =
new ConcreteProblem(cat, pos, msg, sev) problem(cat, pos, msg, sev, None)
def problem(cat: String,
pos: Position,
msg: String,
sev: Severity,
rendered: Option[String]): Problem =
new ConcreteProblem(cat, pos, msg, sev, rendered)
private final class ConcreteT2[A1, A2](a1: A1, a2: A2) extends T2[A1, A2] { private final class ConcreteT2[A1, A2](a1: A1, a2: A2) extends T2[A1, A2] {
val get1: A1 = a1 val get1: A1 = a1
@ -144,12 +152,14 @@ object InterfaceUtil {
cat: String, cat: String,
pos: Position, pos: Position,
msg: String, msg: String,
sev: Severity sev: Severity,
rendered0: Option[String]
) extends Problem { ) extends Problem {
val category = cat val category = cat
val position = pos val position = pos
val message = msg val message = msg
val severity = sev val severity = sev
override val rendered = o2jo(rendered0)
override def toString = s"[$severity] $pos: $message" override def toString = s"[$severity] $pos: $message"
} }
} }

View File

@ -117,6 +117,7 @@ object Logger {
sourceFile0 sourceFile0
) )
@deprecated("Use InterfaceUtil.problem", "1.2.2")
def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem = def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem =
InterfaceUtil.problem(cat, pos, msg, sev) InterfaceUtil.problem(cat, pos, msg, sev)
} }