diff --git a/build.sbt b/build.sbt index 83f953518..bc2ae0216 100644 --- a/build.sbt +++ b/build.sbt @@ -369,7 +369,7 @@ lazy val utilLogging = (project in file("internal") / "util-logging") .enablePlugins(ContrabandPlugin, JsonCodecPlugin) .dependsOn(utilInterface, collectionProj, coreMacrosProj) .settings( - utilCommonSettings, + testedBaseSettings, name := "Util Logging", libraryDependencies ++= Seq( @@ -383,7 +383,6 @@ lazy val utilLogging = (project in file("internal") / "util-logging") sjsonNewScalaJson.value, scalaReflect.value ), - libraryDependencies ++= Seq(scalacheck % "test", scalatest % "test"), Compile / scalacOptions ++= (scalaVersion.value match { case v if v.startsWith("2.12.") => List("-Ywarn-unused:-locals,-explicits,-privates") case _ => List() @@ -397,6 +396,7 @@ lazy val utilLogging = (project in file("internal") / "util-logging") if (name == "Throwable") Nil else old(tpe) }, + Test / fork := true, utilMimaSettings, mimaBinaryIssueFilters ++= Seq( exclude[DirectMissingMethodProblem]("sbt.internal.util.SuccessEvent.copy*"), diff --git a/internal/util-interface/src/main/java/xsbti/Problem.java b/internal/util-interface/src/main/java/xsbti/Problem.java index 7cf0a404e..2d5c091f6 100644 --- a/internal/util-interface/src/main/java/xsbti/Problem.java +++ b/internal/util-interface/src/main/java/xsbti/Problem.java @@ -11,6 +11,8 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +// Note: Update InterfaceUtil.scala as well. + public interface Problem { String category(); diff --git a/internal/util-logging/src/main/scala/sbt/util/InterfaceUtil.scala b/internal/util-logging/src/main/scala/sbt/util/InterfaceUtil.scala index e7f2d33cc..9da7d0efa 100644 --- a/internal/util-logging/src/main/scala/sbt/util/InterfaceUtil.scala +++ b/internal/util-logging/src/main/scala/sbt/util/InterfaceUtil.scala @@ -12,7 +12,17 @@ import java.util.Optional import java.util.function.Supplier import java.{ util => ju } -import xsbti.{ DiagnosticCode, DiagnosticRelatedInformation, Position, Problem, Severity, T2 } +import xsbti.{ + Action, + DiagnosticCode, + DiagnosticRelatedInformation, + Position, + Problem, + Severity, + TextEdit, + WorkspaceEdit, + T2, +} import scala.collection.mutable.ListBuffer @@ -130,6 +140,7 @@ object InterfaceUtil { ): Problem = problem(cat, pos, msg, sev, rendered, None, List.empty[DiagnosticRelatedInformation]) + @deprecated("Use the overload of this method with more arguments", "1.9.0") def problem( cat: String, pos: Position, @@ -139,7 +150,59 @@ object InterfaceUtil { diagnosticCode: Option[DiagnosticCode], diagnosticRelatedInforamation: List[DiagnosticRelatedInformation] ): Problem = - new ConcreteProblem(cat, pos, msg, sev, rendered, diagnosticCode, diagnosticRelatedInforamation) + problem( + cat, + pos, + msg, + sev, + rendered, + diagnosticCode, + diagnosticRelatedInforamation, + List.empty[Action], + ) + + def problem( + cat: String, + pos: Position, + msg: String, + sev: Severity, + rendered: Option[String], + diagnosticCode: Option[DiagnosticCode], + diagnosticRelatedInformation: List[DiagnosticRelatedInformation], + actions: List[Action], + ): Problem = + new ConcreteProblem( + cat, + pos, + msg, + sev, + rendered, + diagnosticCode, + diagnosticRelatedInformation, + actions, + ) + + def action( + title: String, + description: Option[String], + edit: WorkspaceEdit, + ): Action = + new ConcreteAction(title, description, edit) + + def workspaceEdit(changes: List[TextEdit]): WorkspaceEdit = + new ConcreteWorkspaceEdit(changes) + + def textEdit(position: Position, newText: String): TextEdit = + new ConcreteTextEdit(position, newText) + + def diagnosticCode(code: String, explanation: Option[String]): DiagnosticCode = + new ConcreteDiagnosticCode(code, explanation) + + def diagnosticRelatedInformation( + position: Position, + message: String + ): DiagnosticRelatedInformation = + new ConcreteDiagnosticRelatedInformation(position, message) private final class ConcreteT2[A1, A2](a1: A1, a2: A2) extends T2[A1, A2] { val get1: A1 = a1 @@ -187,6 +250,42 @@ object InterfaceUtil { override val startColumn = o2jo(startColumn0) override val endLine = o2jo(endLine0) override val endColumn = o2jo(endColumn0) + override def toString: String = { + val src = sourcePath0 match { + case Some(x) => s"$x" + case None => "none" + } + val line = line0 match { + case Some(x) => s":$x" + case None => "" + } + val offset = offset0 match { + case Some(x) => s":$x" + case None => "" + } + s"""$src$line$offset""" + } + private def toTuple(p: Position) = + ( + p.line, + p.lineContent, + p.offset, + p.pointer, + p.pointerSpace, + p.sourcePath, + p.sourceFile, + p.startOffset, + p.endOffset, + p.startLine, + p.startColumn, + p.endLine, + p.endColumn, + ) + override def hashCode: Int = toTuple(this).## + override def equals(o: Any): Boolean = o match { + case o: Position => toTuple(this) == toTuple(o) + case _ => false + } } private final class ConcreteProblem( @@ -196,7 +295,8 @@ object InterfaceUtil { sev: Severity, rendered0: Option[String], diagnosticCode0: Option[DiagnosticCode], - diagnosticRelatedInformation0: List[DiagnosticRelatedInformation] + diagnosticRelatedInformation0: List[DiagnosticRelatedInformation], + actions0: List[Action], ) extends Problem { val category = cat val position = pos @@ -204,8 +304,116 @@ object InterfaceUtil { val severity = sev override val rendered = o2jo(rendered0) override def diagnosticCode: Optional[DiagnosticCode] = o2jo(diagnosticCode0) - override def diagnosticRelatedInforamation(): ju.List[DiagnosticRelatedInformation] = + override def diagnosticRelatedInformation(): ju.List[DiagnosticRelatedInformation] = l2jl(diagnosticRelatedInformation0) + @deprecated("use diagnosticRelatedInformation", "1.9.0") + override def diagnosticRelatedInforamation(): ju.List[DiagnosticRelatedInformation] = + diagnosticRelatedInformation() + override def actions(): ju.List[Action] = + l2jl(actions0) override def toString = s"[$severity] $pos: $message" + private def toTuple(p: Problem) = + ( + p.category, + p.position, + p.message, + p.severity, + p.rendered, + p.diagnosticCode, + p.diagnosticRelatedInformation, + p.actions, + ) + override def hashCode: Int = toTuple(this).## + override def equals(o: Any): Boolean = o match { + case o: Problem => toTuple(this) == toTuple(o) + case _ => false + } + } + + private final class ConcreteAction( + title0: String, + description0: Option[String], + edit0: WorkspaceEdit, + ) extends Action { + val title: String = title0 + val edit: WorkspaceEdit = edit0 + override def description(): Optional[String] = + o2jo(description0) + override def toString(): String = + s"Action($title0, $description0, $edit0)" + private def toTuple(a: Action) = + ( + a.title, + a.description, + a.edit, + ) + override def hashCode: Int = toTuple(this).## + override def equals(o: Any): Boolean = o match { + case o: Action => toTuple(this) == toTuple(o) + case _ => false + } + } + + private final class ConcreteWorkspaceEdit(changes0: List[TextEdit]) extends WorkspaceEdit { + override def changes(): ju.List[TextEdit] = l2jl(changes0) + override def toString(): String = + s"WorkspaceEdit($changes0)" + private def toTuple(w: WorkspaceEdit) = jl2l(w.changes) + override def hashCode: Int = toTuple(this).## + override def equals(o: Any): Boolean = o match { + case o: WorkspaceEdit => toTuple(this) == toTuple(o) + case _ => false + } + } + + private final class ConcreteTextEdit(position0: Position, newText0: String) extends TextEdit { + val position: Position = position0 + val newText: String = newText0 + override def toString(): String = + s"TextEdit($position, $newText)" + private def toTuple(edit: TextEdit) = + ( + edit.position, + edit.newText, + ) + override def hashCode: Int = toTuple(this).## + override def equals(o: Any): Boolean = o match { + case o: TextEdit => toTuple(this) == toTuple(o) + case _ => false + } + } + + private final class ConcreteDiagnosticCode(code0: String, explanation0: Option[String]) + extends DiagnosticCode { + val code: String = code0 + val explanation: Optional[String] = o2jo(explanation0) + override def toString(): String = s"DiagnosticCode($code)" + private def toTuple(c: DiagnosticCode) = + ( + c.code, + c.explanation, + ) + override def hashCode: Int = toTuple(this).## + override def equals(o: Any): Boolean = o match { + case o: DiagnosticCode => toTuple(this) == toTuple(o) + case _ => false + } + } + + private final class ConcreteDiagnosticRelatedInformation(position0: Position, message0: String) + extends DiagnosticRelatedInformation { + val position: Position = position0 + val message: String = message0 + override def toString(): String = s"DiagnosticRelatedInformation($position, $message)" + private def toTuple(info: DiagnosticRelatedInformation) = + ( + info.position, + info.message, + ) + override def hashCode: Int = toTuple(this).## + override def equals(o: Any): Boolean = o match { + case o: DiagnosticRelatedInformation => toTuple(this) == toTuple(o) + case _ => false + } } } diff --git a/internal/util-logging/src/test/scala/ProblemTest.scala b/internal/util-logging/src/test/scala/ProblemTest.scala new file mode 100644 index 000000000..d81e80fd2 --- /dev/null +++ b/internal/util-logging/src/test/scala/ProblemTest.scala @@ -0,0 +1,204 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt + +import java.net.URI +import hedgehog._ +import hedgehog.runner._ +import _root_.sbt.util.InterfaceUtil +import InterfaceUtil.{ jl2l, jo2o, l2jl } +import xsbti._ + +object ProblemTest extends Properties { + override def tests: List[Test] = List( + property( + "All problems can toString", + genProblem.forAll.map(toStringCheck), + ), + property( + "All problems can be compared structurally", + genProblem.forAll.map(equalityCheck), + ), + property( + "All diagnostic codes can be compared structurally", + genDiagnosticCode.forAll.map(equalityCheck), + ), + property( + "All diagnostic related information can be compared structurally", + genDiagnosticRelatedInformation.forAll.map(equalityCheck), + ), + property( + "All actions can be compared structurally", + genAction.forAll.map(equalityCheck), + ), + ) + + def toStringCheck(p: Problem): Result = + Result.assert(p.toString() != "") + + def equalityCheck(p: Problem): Result = { + val other = InterfaceUtil.problem( + p.category, + p.position, + p.message, + p.severity, + jo2o(p.rendered), + jo2o(p.diagnosticCode).map(copy), + jl2l(p.diagnosticRelatedInformation).map(copy), + jl2l(p.actions).map(copy), + ) + Result + .assert(p == other) + .log(s"$p == $other") + } + + def equalityCheck(c: DiagnosticCode): Result = { + val other = copy(c) + Result.assert(c == other) + } + + def equalityCheck(info: DiagnosticRelatedInformation): Result = { + val other = copy(info) + Result.assert(info == other) + } + + def equalityCheck(a: Action): Result = { + val other = copy(a) + Result.assert(a == other) + } + + lazy val genProblem: Gen[Problem] = + for { + cat <- genString + pos <- genPosition + msg <- genString + sev <- genSeverity + rendered <- optString + code <- optDiagnosticCode + info <- listDiagnosticRelatedInformation + actions <- listAction + } yield InterfaceUtil.problem( + cat, + pos, + msg, + sev, + rendered, + code, + info, + actions, + ) + + lazy val optDiagnosticCode: Gen[Option[DiagnosticCode]] = + Gen.choice1(genDiagnosticCode.map(Some(_)), Gen.constant(None)) + + lazy val genDiagnosticCode: Gen[DiagnosticCode] = + for { + code <- Gen.int(Range.linear(0, 1024)) + } yield InterfaceUtil.diagnosticCode("E" + code.toString, None) + + lazy val genSeverity: Gen[Severity] = + Gen.element(Severity.Info, List(Severity.Warn, Severity.Error)) + + lazy val genPosition: Gen[Position] = + for { + line <- optIntGen + content <- genString + offset <- optIntGen + } yield InterfaceUtil.position( + line, + content, + offset, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ) + + lazy val listDiagnosticRelatedInformation: Gen[List[DiagnosticRelatedInformation]] = + Gen.list(genDiagnosticRelatedInformation, Range.linear(0, 2)) + + lazy val genDiagnosticRelatedInformation: Gen[DiagnosticRelatedInformation] = + for { + pos <- genPosition + message <- genString + } yield InterfaceUtil.diagnosticRelatedInformation(pos, message) + + lazy val listAction: Gen[List[Action]] = + Gen.list(genAction, Range.linear(0, 2)) + + lazy val genAction: Gen[Action] = + for { + title <- genString + description <- optString + edit <- genWorkspaceEdit + } yield InterfaceUtil.action(title, description, edit) + + lazy val genWorkspaceEdit: Gen[WorkspaceEdit] = + for { + changes <- listTextEdit + } yield InterfaceUtil.workspaceEdit(changes) + + lazy val listTextEdit: Gen[List[TextEdit]] = + Gen.list(genTextEdit, Range.linear(0, 2)) + + lazy val genTextEdit: Gen[TextEdit] = + for { + pos <- genPosition + newText <- genString + } yield InterfaceUtil.textEdit(pos, newText) + + lazy val genUri: Gen[URI] = + for { + ssp <- genString + } yield new URI("file", "///" + ssp, null) + + lazy val optString: Gen[Option[String]] = + Gen.choice1(genString.map(Some(_)), Gen.constant(None)) + + lazy val genString = Gen.string(Gen.alphaNum, Range.linear(0, 256)) + + lazy val optIntGen: Gen[Option[Integer]] = + Gen.choice1(Gen.int(Range.linear(0, 1024)).map(Some(_)), Gen.constant(None)) + + private def copy(c: DiagnosticCode): DiagnosticCode = + new DiagnosticCode() { + val code = c.code + override def explanation = c.explanation + } + + private def copy(info: DiagnosticRelatedInformation): DiagnosticRelatedInformation = + new DiagnosticRelatedInformation() { + override def position = info.position + override def message = info.message + } + + private def copy(a: Action): Action = + new Action { + override def title = a.title + override def description = a.description + override def edit = copy(a.edit) + } + + private def copy(edit: WorkspaceEdit): WorkspaceEdit = + new WorkspaceEdit { + override def changes() = + l2jl(jl2l(edit.changes).map(copy)) + } + + private def copy(edit: TextEdit): TextEdit = + new TextEdit { + override val position = edit.position + override val newText = edit.newText + } +}