Skip to content

Commit 15b317b

Browse files
authored
Merge pull request #1373 from vasilmkd/javac-column
Parse column/position information from javac error messages
2 parents e0ef153 + f51114b commit 15b317b

File tree

4 files changed

+147
-35
lines changed

4 files changed

+147
-35
lines changed

internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/DiagnosticsReporter.scala

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ object DiagnosticsReporter {
7272
override val line: Optional[Integer],
7373
override val lineContent: String,
7474
override val offset: Optional[Integer],
75+
override val pointer: Optional[Integer],
76+
override val pointerSpace: Optional[String],
7577
override val startOffset: Optional[Integer],
7678
override val endOffset: Optional[Integer],
7779
override val startLine: Optional[Integer],
@@ -81,8 +83,6 @@ object DiagnosticsReporter {
8183
) extends xsbti.Position {
8284
override val sourcePath: Optional[String] = o2jo(sourceUri)
8385
override val sourceFile: Optional[File] = o2jo(sourceUri.map(new File(_)))
84-
override val pointer: Optional[Integer] = o2jo(Option.empty[Integer])
85-
override val pointerSpace: Optional[String] = o2jo(Option.empty[String])
8686

8787
override def toString: String =
8888
if (sourceUri.isDefined) s"${sourceUri.get}:${if (line.isPresent) line.get else -1}"
@@ -200,6 +200,8 @@ object DiagnosticsReporter {
200200
def endPosition: Option[Long] = checkNoPos(d.getEndPosition)
201201

202202
val line: Optional[Integer] = o2jo(checkNoPos(d.getLineNumber) map (_.toInt))
203+
// column number is 1-based, xsbti.Position#pointer is 0-based.
204+
val pointer: Optional[Integer] = o2jo(checkNoPos(d.getColumnNumber - 1) map (_.toInt))
203205
val offset: Optional[Integer] = o2jo(checkNoPos(d.getPosition) map (_.toInt))
204206
val startOffset: Optional[Integer] = o2jo(startPosition map (_.toInt))
205207
val endOffset: Optional[Integer] = o2jo(endPosition map (_.toInt))
@@ -241,11 +243,20 @@ object DiagnosticsReporter {
241243
case _ => noPositionInfo
242244
}
243245

246+
val pointerSpace = pointer.map[String] { p =>
247+
lineContent.take(p.intValue()).map {
248+
case '\t' => '\t'
249+
case _ => ' '
250+
}
251+
}
252+
244253
new PositionImpl(
245254
sourcePath,
246255
line,
247256
lineContent,
248257
offset,
258+
pointer,
259+
pointerSpace,
249260
startOffset,
250261
endOffset,
251262
startLine,

internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/JavaErrorParser.scala

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,21 @@ import sbt.util.InterfaceUtil.o2jo
2222
import xsbti.{ Problem, Severity, Position }
2323

2424
/** A wrapper around xsbti.Position so we can pass in Java input. */
25-
final case class JavaPosition(_sourceFilePath: String, _line: Int, _contents: String, _offset: Int)
26-
extends Position {
25+
final case class JavaPosition(
26+
_sourceFilePath: String,
27+
_line: Int,
28+
_contents: String,
29+
_pointer: Int,
30+
_pointerSpace: String
31+
) extends Position {
2732
def line: Optional[Integer] = o2jo(Some(_line))
2833
def lineContent: String = _contents
29-
def offset: Optional[Integer] = o2jo(Some(_offset))
30-
def pointer: Optional[Integer] = o2jo(None)
31-
def pointerSpace: Optional[String] = o2jo(None)
34+
def offset: Optional[Integer] = o2jo(None)
35+
def pointer: Optional[Integer] = o2jo(Some(_pointer))
36+
def pointerSpace: Optional[String] = o2jo(Option(_pointerSpace))
3237
def sourcePath: Optional[String] = o2jo(Option(_sourceFilePath))
3338
def sourceFile: Optional[File] = o2jo(Option(new File(_sourceFilePath)))
34-
override def toString = s"${_sourceFilePath}:${_line}:${_offset}"
39+
override def toString = s"${_sourceFilePath}:${_line}:${_pointer}"
3540
}
3641

3742
/** A position which has no information, because there is none. */
@@ -181,6 +186,11 @@ class JavaErrorParser(relativeDir: File = new File(new File(".").getAbsolutePath
181186
}
182187
fileLineMessage ~ (allUntilCaret ~ '^' ~ restOfLine).? ~ (nonPathLines.?) ^^ {
183188
case (file, line, msg) ~ contentsOpt ~ ind =>
189+
val (pointer, pointerSpace) = contentsOpt match {
190+
case Some(contents ~ _ ~ _) => getPointer(contents)
191+
case _ => (0, "")
192+
}
193+
184194
new JavaProblem(
185195
new JavaPosition(
186196
findFileSource(file),
@@ -190,10 +200,8 @@ class JavaErrorParser(relativeDir: File = new File(new File(".").getAbsolutePath
190200
case _ => ""
191201
}) + ind
192202
.getOrElse(""), // TODO - Actually parse caret position out of here.
193-
(contentsOpt match {
194-
case Some(contents ~ _ ~ _) => getOffset(contents)
195-
case _ => 0
196-
})
203+
pointer,
204+
pointerSpace
197205
),
198206
Severity.Error,
199207
msg
@@ -208,6 +216,11 @@ class JavaErrorParser(relativeDir: File = new File(new File(".").getAbsolutePath
208216
}
209217
fileLineMessage ~ (allUntilCaret ~ '^' ~ restOfLine).? ~ (nonPathLines.?) ^^ {
210218
case (file, line, msg) ~ contentsOpt ~ ind =>
219+
val (pointer, pointerSpace) = contentsOpt match {
220+
case Some(contents ~ _ ~ _) => getPointer(contents)
221+
case _ => (0, "")
222+
}
223+
211224
new JavaProblem(
212225
new JavaPosition(
213226
findFileSource(file),
@@ -216,10 +229,8 @@ class JavaErrorParser(relativeDir: File = new File(new File(".").getAbsolutePath
216229
case Some(contents ~ _ ~ r) => contents + '^' + r
217230
case _ => ""
218231
}) + ind.getOrElse(""),
219-
(contentsOpt match {
220-
case Some(contents ~ _ ~ _) => getOffset(contents)
221-
case _ => 0
222-
})
232+
pointer,
233+
pointerSpace
223234
),
224235
Severity.Warn,
225236
msg
@@ -292,7 +303,6 @@ class JavaErrorParser(relativeDir: File = new File(new File(".").getAbsolutePath
292303
Seq.empty
293304
}
294305

295-
private def getOffset(contents: String): Int =
296-
contents.linesIterator.toList.lastOption map (_.length) getOrElse 0
297-
306+
private def getPointer(contents: String): (Int, String) =
307+
contents.linesIterator.toList.lastOption.map(line => (line.length, line)).getOrElse((0, ""))
298308
}

internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/JavaCompilerSpec.scala

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,17 @@ class JavaCompilerSpec extends UnitSpec with Diagrams {
114114
case _ => 5
115115
}
116116
})
117-
val importWarn = warnOnLine(lineno = 12, lineContent = Some("java.rmi.RMISecurityException"))
118-
val enclosingError = errorOnLine(lineno = 25, message = Some("not an enclosing class: C.D"))
117+
val importWarn =
118+
warnOnLine(lineno = 12, colno = 15, lineContent = Some("java.rmi.RMISecurityException"))
119+
val enclosingError =
120+
errorOnLine(lineno = 25, colno = 9, message = Some("not an enclosing class: C.D"))
119121
val beAnExpectedError =
120122
List(
121123
importWarn,
122-
errorOnLine(14),
123-
errorOnLine(15),
124-
warnOnLine(18),
124+
errorOnLine(14, 7),
125+
errorOnLine(15, 11),
126+
warnOnLine(18, 18),
127+
warnOnLine(18, 14), // could be expected depending on Java version
125128
enclosingError
126129
) reduce (_ or _)
127130
problems foreach { p =>
@@ -140,7 +143,7 @@ class JavaCompilerSpec extends UnitSpec with Diagrams {
140143
}
141144
})
142145
assert(problems.size == 2)
143-
val beAnExpectedError = List(errorOnLine(14), errorOnLine(15)) reduce (_ or _)
146+
val beAnExpectedError = List(errorOnLine(14, 7), errorOnLine(15, 11)) reduce (_ or _)
144147
problems foreach { p =>
145148
p should beAnExpectedError
146149
}
@@ -192,20 +195,43 @@ class JavaCompilerSpec extends UnitSpec with Diagrams {
192195
lineNumberCheck && messageCheck
193196
}
194197

195-
def lineMatches(p: Problem, lineno: Int, lineContent: Option[String] = None): Boolean = {
198+
def lineMatches(
199+
p: Problem,
200+
lineno: Int,
201+
colno: Int,
202+
lineContent: Option[String] = None
203+
): Boolean = {
196204
def lineContentCheck = lineContent forall (content => p.position.lineContent contains content)
197205
def lineNumberCheck = p.position.line.isPresent && (p.position.line.get == lineno)
198-
lineNumberCheck && lineContentCheck
206+
def columnCheck = {
207+
val res = p.position.pointer.isPresent && (p.position.pointer.get == colno)
208+
if (!res) {
209+
println(s"got ${p.position().pointer().get}, expected $colno, problem $p")
210+
}
211+
res
212+
}
213+
lineNumberCheck && columnCheck && lineContentCheck
199214
}
200215

201-
def errorOnLine(lineno: Int, message: Option[String] = None, lineContent: Option[String] = None) =
202-
problemOnLine(lineno, Severity.Error, message, lineContent)
216+
def errorOnLine(
217+
lineno: Int,
218+
colno: Int,
219+
message: Option[String] = None,
220+
lineContent: Option[String] = None
221+
) =
222+
problemOnLine(lineno, colno, Severity.Error, message, lineContent)
203223

204-
def warnOnLine(lineno: Int, message: Option[String] = None, lineContent: Option[String] = None) =
205-
problemOnLine(lineno, Severity.Warn, message, lineContent)
224+
def warnOnLine(
225+
lineno: Int,
226+
colno: Int,
227+
message: Option[String] = None,
228+
lineContent: Option[String] = None
229+
) =
230+
problemOnLine(lineno, colno, Severity.Warn, message, lineContent)
206231

207232
private def problemOnLine(
208233
lineno: Int,
234+
colno: Int,
209235
severity: Severity,
210236
message: Option[String],
211237
lineContent: Option[String]
@@ -218,9 +244,10 @@ class JavaCompilerSpec extends UnitSpec with Diagrams {
218244
messageMatches(p, lineno, message) && lineMatches(
219245
p,
220246
lineno,
247+
colno,
221248
lineContent
222249
) && p.severity == severity,
223-
s"Expected $problemType on line $lineno$msg$content, but found $p",
250+
s"Expected $problemType on line $lineno, column $colno$msg$content, but found $p",
224251
"Problem matched: " + p
225252
)
226253
}

internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/javaErrorParserSpec.scala

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,79 @@ class JavaErrorParserSpec extends UnitSpec with Diagrams {
8080
val logger = ConsoleLogger()
8181
val problems = parser.parseProblems(sampleErrorPosition, logger)
8282
assert(problems.size == 1)
83-
problems(0).position.offset.isPresent shouldBe true
84-
problems(0).position.offset.get shouldBe 23
83+
val position = problems.head.position
84+
85+
position.sourcePath.isPresent shouldBe true
86+
position.sourcePath.get shouldBe "/home/me/projects/sample/src/main/java/A.java"
87+
position.line.isPresent shouldBe true
88+
position.line.get shouldBe 6
89+
position.lineContent should include("System.out.println(foobar);")
90+
position.pointer.isPresent shouldBe true
91+
position.pointer.get shouldBe 23
92+
position.pointerSpace.isPresent shouldBe true
93+
position.pointerSpace.get shouldBe (" " * 23)
8594
}
8695

8796
def parseMultipleErrors() = {
8897
val parser = new JavaErrorParser()
8998
val logger = ConsoleLogger()
9099
val problems = parser.parseProblems(sampleMultipleErrors, logger)
91100
assert(problems.size == 5)
101+
102+
val position1 = problems(0).position
103+
position1.sourcePath.isPresent shouldBe true
104+
position1.sourcePath.get shouldBe "/home/foo/sbt/internal/inc/javac/test1.java"
105+
position1.line.isPresent shouldBe true
106+
position1.line.get shouldBe 3
107+
position1.lineContent should include("public class Test {")
108+
position1.pointer.isPresent shouldBe true
109+
position1.pointer.get shouldBe 7
110+
position1.pointerSpace.isPresent shouldBe true
111+
position1.pointerSpace.get shouldBe (" " * 7)
112+
113+
val position2 = problems(1).position
114+
position2.sourcePath.isPresent shouldBe true
115+
position2.sourcePath.get shouldBe "/home/foo/sbt/internal/inc/javac/test1.java"
116+
position2.line.isPresent shouldBe true
117+
position2.line.get shouldBe 1
118+
position2.lineContent should include("import java.rmi.RMISecurityException;")
119+
position2.pointer.isPresent shouldBe true
120+
position2.pointer.get shouldBe 15
121+
position2.pointerSpace.isPresent shouldBe true
122+
position2.pointerSpace.get shouldBe (" " * 15)
123+
124+
val position3 = problems(2).position
125+
position3.sourcePath.isPresent shouldBe true
126+
position3.sourcePath.get shouldBe "/home/foo/sbt/internal/inc/javac/test1.java"
127+
position3.line.isPresent shouldBe true
128+
position3.line.get shouldBe 4
129+
position3.lineContent should include("public NotFound foo() { return 5; }")
130+
position3.pointer.isPresent shouldBe true
131+
position3.pointer.get shouldBe 11
132+
position3.pointerSpace.isPresent shouldBe true
133+
position3.pointerSpace.get shouldBe (" " * 11)
134+
135+
val position4 = problems(3).position
136+
position4.sourcePath.isPresent shouldBe true
137+
position4.sourcePath.get shouldBe "/home/foo/sbt/internal/inc/javac/test1.java"
138+
position4.line.isPresent shouldBe true
139+
position4.line.get shouldBe 7
140+
position4.lineContent should include("""throw new RMISecurityException("O NOES");""")
141+
position4.pointer.isPresent shouldBe true
142+
position4.pointer.get shouldBe 18
143+
position4.pointerSpace.isPresent shouldBe true
144+
position4.pointerSpace.get shouldBe (" " * 18)
145+
146+
val position5 = problems(4).position
147+
position5.sourcePath.isPresent shouldBe true
148+
position5.sourcePath.get shouldBe "/home/foo/sbt/internal/inc/javac/test1.java"
149+
position5.line.isPresent shouldBe true
150+
position5.line.get shouldBe 7
151+
position5.lineContent should include("""throw new RMISecurityException("O NOES");""")
152+
position5.pointer.isPresent shouldBe true
153+
position5.pointer.get shouldBe 14
154+
position5.pointerSpace.isPresent shouldBe true
155+
position5.pointerSpace.get shouldBe (" " * 14)
92156
}
93157

94158
def parseMultipleErrors2() = {
@@ -144,7 +208,7 @@ class JavaErrorParserSpec extends UnitSpec with Diagrams {
144208

145209
def sampleErrorPosition =
146210
"""
147-
|A.java:6: cannot find symbol
211+
|/home/me/projects/sample/src/main/java/A.java:6: cannot find symbol
148212
|symbol : variable foobar
149213
|location: class A
150214
| System.out.println(foobar);

0 commit comments

Comments
 (0)