Skip to content

Commit 61a74c5

Browse files
BarkingBadpikinier20
authored andcommitted
Add returning real line of error in source file of snippet for snippet scaladoc compiler
1 parent 51120be commit 61a74c5

File tree

10 files changed

+97
-57
lines changed

10 files changed

+97
-57
lines changed

scaladoc-testcases/src/tests/snippetCompilerTest.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package snippetCompiler
33
/**
44
* ```scala sc:compile
55
* def a = 2
6+
* val x = 1 + List()
67
* a
78
* ```
89
*
@@ -11,4 +12,4 @@ package snippetCompiler
1112
* a()
1213
* ```
1314
*/
14-
class A { }
15+
class A { }

scaladoc/src/dotty/tools/scaladoc/api.scala

+9-5
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,15 @@ extension (s: Signature)
232232
case l: Link => l.name
233233
}.mkString
234234

235-
case class TastyMemberSource(val path: java.nio.file.Path, val lineNumber: Int)
235+
case class TastyMemberSource(path: java.nio.file.Path, lineNumber: Int)
236+
237+
object SnippetCompilerData:
238+
case class Position(line: Int, column: Int)
236239

237240
case class SnippetCompilerData(
238-
val packageName: String,
239-
val classType: Option[String],
240-
val classGenerics: Option[String],
241-
val imports: List[String]
241+
packageName: String,
242+
classType: Option[String],
243+
classGenerics: Option[String],
244+
imports: List[String],
245+
position: SnippetCompilerData.Position
242246
)

scaladoc/src/dotty/tools/scaladoc/renderers/WikiDocRenderer.scala

+16-16
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,22 @@ import dotty.tools.scaladoc.snippets._
1010

1111
class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using ctx: DocContext):
1212

13-
private val snippetCheckingFunc: Member => (String, Option[SnippetCompilerArg]) => Unit =
14-
(m: Member) => {
15-
(str: String, argOverride: Option[SnippetCompilerArg]) => {
16-
val arg = argOverride.fold(
17-
ctx.snippetCompilerArgs.get(m).fold(SnippetCompilerArg.default)(p => p)
18-
)(p => p)
19-
20-
snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData), arg).foreach { _ match {
21-
case r @ SnippetCompilationResult(None, _) =>
22-
println(s"In member ${m.name} (${m.dri.location}):")
23-
println(r.getSummary)
24-
case _ =>
13+
private val snippetCheckingFuncFromMember: Member => SnippetChecker.SnippetCheckingFunc =
14+
(m: Member) => {
15+
(str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SnippetCompilerArg]) => {
16+
val arg = argOverride.getOrElse(
17+
ctx.snippetCompilerArgs.get(m).getOrElse(SnippetCompilerArg.default)
18+
)
19+
20+
snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData), arg, lineOffset).foreach { _ match {
21+
case r @ SnippetCompilationResult(None, _) =>
22+
println(s"In member ${m.name} (${m.dri.location}):")
23+
println(r.getSummary)
24+
case _ =>
25+
}
2526
}
26-
}
27+
}
2728
}
28-
}
2929

3030
def renderDocPart(doc: DocPart)(using Member): AppliedTag = doc match
3131
case md: MdNode => renderMarkdown(md)
@@ -37,7 +37,7 @@ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChe
3737
raw(DocFlexmarkRenderer.render(el)(
3838
(link,name) =>
3939
renderLink(link, default => text(if name.isEmpty then default else name)).toString,
40-
snippetCheckingFunc(m)
40+
snippetCheckingFuncFromMember(m)
4141
))
4242

4343
private def listItems(items: Seq[WikiDocElement])(using m: Member) =
@@ -67,7 +67,7 @@ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChe
6767
case 6 => h6(content)
6868
case Paragraph(text) => p(renderElement(text))
6969
case Code(data: String) =>
70-
snippetCheckingFunc(m)(data, None)
70+
snippetCheckingFuncFromMember(m)(data, 0, None)
7171
pre(code(raw(data))) // TODO add classes
7272
case HorizontalRule => hr
7373

scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala

+12-5
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,25 @@ class SnippetChecker()(using ctx: DocContext):
1010
Paths.get(ctx.args.classpath).toAbsolutePath + sep +
1111
ctx.args.tastyDirs.map(_.getAbsolutePath()).mkString(sep)
1212
private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp)
13-
private val wrapper: SnippetWrapper = SnippetWrapper()
13+
1414
var warningsCount = 0
1515
var errorsCount = 0
1616

1717
def checkSnippet(
1818
snippet: String,
1919
data: Option[SnippetCompilerData],
20-
arg: SnippetCompilerArg
20+
arg: SnippetCompilerArg,
21+
lineOffset: SnippetChecker.LineOffset
2122
): Option[SnippetCompilationResult] = {
2223
if arg.is(SCFlags.Compile) then
23-
val wrapped = wrapper.wrap(
24+
val wrapped = WrappedSnippet(
2425
snippet,
2526
data.map(_.packageName),
2627
data.flatMap(_.classType),
2728
data.flatMap(_.classGenerics),
28-
data.map(_.imports).getOrElse(Nil)
29+
data.map(_.imports).getOrElse(Nil),
30+
lineOffset + data.fold(0)(_.position.line) + 1,
31+
data.fold(0)(_.position.column)
2932
)
3033
val res = compiler.compile(wrapped)
3134
if !res.messages.filter(_.level == MessageLevel.Error).isEmpty then errorsCount = errorsCount + 1
@@ -38,4 +41,8 @@ class SnippetChecker()(using ctx: DocContext):
3841
|Snippet compiler summary:
3942
| Found $warningsCount warnings
4043
| Found $errorsCount errors
41-
|""".stripMargin
44+
|""".stripMargin
45+
46+
object SnippetChecker:
47+
type LineOffset = Int
48+
type SnippetCheckingFunc = (String, LineOffset, Option[SnippetCompilerArg]) => Unit

scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala

+5-11
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ class SnippetCompiler(
4242
private def nullableMessage(msgOrNull: String): String =
4343
if (msgOrNull == null) "" else msgOrNull
4444

45-
private def createReportMessage(diagnostics: Seq[Diagnostic]): Seq[SnippetCompilerMessage] = {
45+
private def createReportMessage(diagnostics: Seq[Diagnostic], line: Int, column: Int): Seq[SnippetCompilerMessage] = {
4646
val infos = diagnostics.toSeq.sortBy(_.pos.source.path)
4747
val errorMessages = infos.map {
4848
case diagnostic if diagnostic.position.isPresent =>
4949
val pos = diagnostic.position.get
5050
val msg = nullableMessage(diagnostic.message)
5151
val level = MessageLevel.fromOrdinal(diagnostic.level)
52-
SnippetCompilerMessage(pos.line, pos.column, pos.lineContent, msg, level)
52+
SnippetCompilerMessage(pos.line + line, pos.column + column, pos.lineContent, msg, level)
5353
case d =>
5454
val level = MessageLevel.fromOrdinal(d.level)
5555
SnippetCompilerMessage(-1, -1, "", nullableMessage(d.message), level)
@@ -58,7 +58,7 @@ class SnippetCompiler(
5858
}
5959

6060
def compile(
61-
snippets: List[String]
61+
wrappedSnippet: WrappedSnippet
6262
): SnippetCompilationResult = {
6363
val context = driver.currentCtx.fresh
6464
.setSetting(
@@ -67,14 +67,8 @@ class SnippetCompiler(
6767
)
6868
.setReporter(new StoreReporter)
6969
val run = newRun(using context)
70-
run.compileFromStrings(snippets)
71-
val messages = createReportMessage(context.reporter.pendingMessages(using context))
70+
run.compileFromStrings(List(wrappedSnippet.snippet))
71+
val messages = createReportMessage(context.reporter.pendingMessages(using context), wrappedSnippet.lineOffset, wrappedSnippet.columnOffset)
7272
val targetIfSuccessful = Option.when(!context.reporter.hasErrors)(target)
7373
SnippetCompilationResult(targetIfSuccessful, messages)
7474
}
75-
76-
def compile(
77-
snippet: String
78-
): SnippetCompilationResult = compile(List(snippet))
79-
80-

scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,4 @@ object SnippetCompilerArgParser extends ArgParser[SnippetCompilerArg]:
7777
}.toList
7878

7979
if !allErrors.isEmpty then Left(allErrors.mkString("\n")) else Right(SnippetCompilerArg(checkedFlags))
80-
}
80+
}

scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala renamed to scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala

+22-10
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,40 @@ package snippets
44
import java.io.ByteArrayOutputStream
55
import java.io.PrintStream
66

7-
class SnippetWrapper:
8-
extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) =
9-
ps.println((" " * indent) + str)
10-
def wrap(str: String): String =
7+
case class WrappedSnippet(snippet: String, lineOffset: Int, columnOffset: Int)
8+
9+
object WrappedSnippet:
10+
private val lineOffset = 2
11+
private val columnOffset = 2
12+
13+
def apply(str: String): WrappedSnippet =
1114
val baos = new ByteArrayOutputStream()
1215
val ps = new PrintStream(baos)
1316
ps.println("package snippets")
1417
ps.println("object Snippet {")
1518
str.split('\n').foreach(ps.printlnWithIndent(2, _))
1619
ps.println("}")
17-
baos.toString
20+
WrappedSnippet(baos.toString, lineOffset, columnOffset)
1821

19-
def wrap(str:String, packageName: Option[String], className: Option[String], classGenerics: Option[String], imports: List[String]) =
22+
def apply(
23+
str: String,
24+
packageName: Option[String],
25+
className: Option[String],
26+
classGenerics: Option[String],
27+
imports: List[String],
28+
lineOffset: Int,
29+
columnOffset: Int
30+
): WrappedSnippet =
2031
val baos = new ByteArrayOutputStream()
2132
val ps = new PrintStream(baos)
2233
ps.println(s"package ${packageName.getOrElse("snippets")}")
2334
imports.foreach(i => ps.println(s"import $i"))
2435
ps.println(s"trait Snippet${classGenerics.getOrElse("")} { ${className.fold("")(cn => s"self: $cn =>")}")
2536
str.split('\n').foreach(ps.printlnWithIndent(2, _))
2637
ps.println("}")
27-
baos.toString
38+
WrappedSnippet(baos.toString, lineOffset, columnOffset)
39+
40+
extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) =
41+
ps.println((" " * indent) + str)
42+
2843

29-
object SnippetWrapper:
30-
private val lineOffset = 2
31-
private val columnOffset = 2

scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala

-1
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,3 @@ trait SyntheticsSupport:
114114
typeForClass(c).asInstanceOf[dotc.core.Types.Type]
115115
.memberInfo(symbol.asInstanceOf[dotc.core.Symbols.Symbol])
116116
.asInstanceOf[TypeRepr]
117-

scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala

+24-3
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,31 @@ abstract class MarkupConversion[T](val repr: Repr)(using DocContext) {
147147
createTypeConstructor(t.asInstanceOf[dotc.core.Types.TypeRef].underlying)
148148
).mkString("[",", ","]")
149149
)
150-
SnippetCompilerData(packageName, classType, classGenerics, Nil)
150+
SnippetCompilerData(packageName, classType, classGenerics, Nil, position(hackGetPositionOfDocstring(using qctx)(sym)))
151151
case _ => getSnippetCompilerData(sym.maybeOwner)
152-
} else SnippetCompilerData(packageName, None, None, Nil)
153-
152+
} else SnippetCompilerData(packageName, None, None, Nil, position(hackGetPositionOfDocstring(using qctx)(sym)))
153+
154+
private def position(p: Option[qctx.reflect.Position]): SnippetCompilerData.Position =
155+
p.fold(SnippetCompilerData.Position(0, 0))(p => SnippetCompilerData.Position(p.startLine, p.startColumn))
156+
157+
private def hackGetPositionOfDocstring(using Quotes)(s: qctx.reflect.Symbol): Option[qctx.reflect.Position] =
158+
import dotty.tools.dotc.core.Comments.CommentsContext
159+
import dotty.tools.dotc
160+
given ctx: dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
161+
val docCtx = ctx.docCtx.getOrElse {
162+
throw new RuntimeException(
163+
"DocCtx could not be found and documentations are unavailable. This is a compiler-internal error."
164+
)
165+
}
166+
val span = docCtx.docstring(s.asInstanceOf[dotc.core.Symbols.Symbol]).span
167+
s.pos.flatMap { pos =>
168+
docCtx.docstring(s.asInstanceOf[dotc.core.Symbols.Symbol]).map { docstring =>
169+
dotty.tools.dotc.util.SourcePosition(
170+
pos.sourceFile.asInstanceOf[dotty.tools.dotc.util.SourceFile],
171+
docstring.span
172+
).asInstanceOf[qctx.reflect.Position]
173+
}
174+
}
154175

155176
final def parse(preparsed: PreparsedComment): Comment =
156177
val body = markupToDokkaCommentBody(stringToMarkup(preparsed.body))

scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala

+6-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import com.vladsch.flexmark.util.options._
1212
import com.vladsch.flexmark.util.sequence.BasedSequence
1313
import com.vladsch.flexmark._
1414

15+
import dotty.tools.scaladoc.snippets.SnippetChecker
16+
1517
class DocLinkNode(
1618
val target: DocLink,
1719
val body: String,
@@ -45,7 +47,7 @@ object DocFlexmarkParser {
4547
}
4648
}
4749

48-
case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String, Option[snippets.SnippetCompilerArg]) => Unit)
50+
case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetCheckingFunc: SnippetChecker.SnippetCheckingFunc)
4951
extends HtmlRenderer.HtmlRendererExtension:
5052

5153
def rendererOptions(opt: MutableDataHolder): Unit = () // noop
@@ -59,7 +61,7 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetC
5961
.map(_.stripPrefix("sc:"))
6062
.map(snippets.SnippetCompilerArgParser.parse)
6163
.flatMap(_.toOption)
62-
snippetCheckingFunc(node.getContentChars.toString, argOverride)
64+
snippetCheckingFunc(node.getContentChars.toString, node.getStartLineNumber, argOverride)
6365
c.delegateRender()
6466

6567
object Handler extends CustomNodeRenderer[DocLinkNode]:
@@ -80,6 +82,6 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetC
8082
htmlRendererBuilder.nodeRendererFactory(Factory)
8183

8284
object DocFlexmarkRenderer:
83-
def render(node: Node)(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String, Option[snippets.SnippetCompilerArg]) => Unit) =
85+
def render(node: Node)(renderLink: (DocLink, String) => String, snippetCheckingFunc: SnippetChecker.SnippetCheckingFunc) =
8486
val opts = MarkdownParser.mkMarkdownOptions(Seq(DocFlexmarkRenderer(renderLink, snippetCheckingFunc)))
85-
HtmlRenderer.builder(opts).build().render(node)
87+
HtmlRenderer.builder(opts).build().render(node)

0 commit comments

Comments
 (0)