Skip to content

Commit 45c3160

Browse files
committed
Run snippet compiler on static sites
1 parent 0f870aa commit 45c3160

16 files changed

+168
-91
lines changed

scaladoc-testcases/docs/docs/index.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
3+
4+
---
5+
6+
```scala sc:compile
7+
2 + List(0)
8+
```
9+

scaladoc-testcases/src/tests/snippetCompilerTest.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ package snippetCompiler
2727
* val d: Long = "asd"
2828
* ```
2929
*
30-
* ```scala sc:failing
30+
* ```scala sc:fail
3131
* def a = 2
3232
* val x = 1 + List()
3333
* a
3434
* ```
3535
*
36-
* ```scala sc:failing
36+
* ```scala sc:fail
3737
* def a = 2
3838
* ```
3939
*
@@ -45,7 +45,6 @@ package snippetCompiler
4545
class A { }
4646

4747
/**
48-
*
4948
* ```scala sc:compile
5049
* val c: Int = 4.5
5150
* ```

scaladoc/src/dotty/tools/scaladoc/DocContext.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,15 @@ case class DocContext(args: Scaladoc.Args, compilerContext: CompilerContext):
7373

7474
lazy val snippetCompilerArgs = snippets.SnippetCompilerArgs.load(args.snippetCompiler, args.snippetCompilerDebug)(using compilerContext)
7575

76+
lazy val snippetChecker = snippets.SnippetChecker(args.classpath, args.tastyDirs)
77+
7678
lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext(
7779
File(path).getAbsoluteFile(),
7880
args,
79-
sourceLinks
81+
sourceLinks,
82+
snippetCompilerArgs,
83+
snippetChecker
8084
)(using compilerContext))
8185

86+
8287
val externalDocumentationLinks = args.externalMappings

scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,4 @@ case class LoadedTemplate(
5151
("site" -> (getMap("site") + ("posts" -> posts))) + ("urls" -> sourceLinks.toMap) +
5252
("page" -> (getMap("page") + ("title" -> templateFile.title)))
5353

54-
templateFile.resolveInner(RenderingContext(updatedSettings, ctx.layouts))
54+
templateFile.resolveInner(RenderingContext(updatedSettings, ctx.layouts))(using ctx)

scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import collection.JavaConverters._
1313
class StaticSiteContext(
1414
val root: File,
1515
val args: Scaladoc.Args,
16-
val sourceLinks: SourceLinks)(using val outerCtx: CompilerContext):
16+
val sourceLinks: SourceLinks,
17+
val snippetCompilerArgs: snippets.SnippetCompilerArgs,
18+
val snippetChecker: snippets.SnippetChecker)(using val outerCtx: CompilerContext):
1719

1820
var memberLinkResolver: String => Option[DRI] = _ => None
1921

scaladoc/src/dotty/tools/scaladoc/site/common.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ val defaultMarkdownOptions: DataHolder =
3636
EmojiExtension.create(),
3737
YamlFrontMatterExtension.create(),
3838
StrikethroughExtension.create(),
39-
WikiLinkExtension.create()
39+
WikiLinkExtension.create(),
40+
tasty.comments.markdown.SnippetRenderingExtension
4041
))
4142

4243
def emptyTemplate(file: File, title: String): TemplateFile = TemplateFile(

scaladoc/src/dotty/tools/scaladoc/site/templates.scala

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package dotty.tools.scaladoc
22
package site
33

44
import java.io.File
5-
import java.nio.file.Files
5+
import java.nio.file.{Files, Paths}
66

77
import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension
88
import com.vladsch.flexmark.ext.autolink.AutolinkExtension
@@ -18,6 +18,7 @@ import liqp.Template
1818
import scala.collection.JavaConverters._
1919

2020
import scala.io.Source
21+
import dotty.tools.scaladoc.snippets._
2122

2223
case class RenderingContext(
2324
properties: Map[String, Object],
@@ -55,7 +56,22 @@ case class TemplateFile(
5556
):
5657
def isIndexPage() = file.isFile && (file.getName == "index.md" || file.getName == "index.html")
5758

58-
private[site] def resolveInner(ctx: RenderingContext): ResolvedPage =
59+
private[site] def resolveInner(ctx: RenderingContext)(using ssctx: StaticSiteContext): ResolvedPage =
60+
61+
lazy val snippetCheckingFunc: SnippetChecker.SnippetCheckingFunc =
62+
val path = Some(Paths.get(file.getAbsolutePath))
63+
val pathBasedArg = ssctx.snippetCompilerArgs.get(path)
64+
(str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => {
65+
val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_))
66+
ssctx.snippetChecker.checkSnippet(str, None, arg, lineOffset).collect {
67+
case r: SnippetCompilationResult if !r.isSuccessful =>
68+
val msg = s"In static site (${file.getAbsolutePath}):\n${r.getSummary}"
69+
report.error(msg)(using ssctx.outerCtx)
70+
r
71+
case r => r
72+
}
73+
}
74+
5975
if (ctx.resolving.contains(file.getAbsolutePath))
6076
throw new RuntimeException(s"Cycle in templates involving $file: ${ctx.resolving}")
6177

@@ -74,10 +90,13 @@ case class TemplateFile(
7490
val rendered = Template.parse(this.rawCode).render(mutableProperties)
7591
// We want to render markdown only if next template is html
7692
val code = if (isHtml || layoutTemplate.exists(!_.isHtml)) rendered else
93+
// Snippet compiler currently supports markdown only
7794
val parser: Parser = Parser.builder(defaultMarkdownOptions).build()
78-
HtmlRenderer.builder(defaultMarkdownOptions).build().render(parser.parse(rendered))
95+
val parsedMd = parser.parse(rendered)
96+
val processed = FlexmarkSnippetProcessor.processSnippets(parsedMd, ssctx.snippetCompilerArgs.debug, snippetCheckingFunc)
97+
HtmlRenderer.builder(defaultMarkdownOptions).build().render(processed)
7998

8099
layoutTemplate match
81100
case None => ResolvedPage(code, resources ++ ctx.resources)
82101
case Some(layoutTemplate) =>
83-
layoutTemplate.resolveInner(ctx.nest(code, file, resources))
102+
layoutTemplate.resolveInner(ctx.nest(code, file, resources))
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package dotty.tools.scaladoc
2+
package snippets
3+
4+
import com.vladsch.flexmark.util.{ast => mdu, sequence}
5+
import com.vladsch.flexmark.{ast => mda}
6+
import com.vladsch.flexmark.formatter.Formatter
7+
import com.vladsch.flexmark.util.options.MutableDataSet
8+
import collection.JavaConverters._
9+
10+
import dotty.tools.scaladoc.tasty.comments.markdown.ExtendedFencedCodeBlock
11+
12+
object FlexmarkSnippetProcessor:
13+
def processSnippets(root: mdu.Node, debug: Boolean, checkingFunc: => SnippetChecker.SnippetCheckingFunc): mdu.Node = {
14+
lazy val cf: SnippetChecker.SnippetCheckingFunc = checkingFunc
15+
16+
val nodes = root.getDescendants().asScala.collect {
17+
case fcb: mda.FencedCodeBlock => fcb
18+
}.toList
19+
20+
nodes.foreach { node =>
21+
val snippet = node.getContentChars.toString
22+
val lineOffset = node.getStartLineNumber
23+
val info = node.getInfo.toString
24+
val argOverride =
25+
info.split(" ")
26+
.find(_.startsWith("sc:"))
27+
.map(_.stripPrefix("sc:"))
28+
.map(SCFlagsParser.parse)
29+
.flatMap(_.toOption)
30+
val snippetCompilationResult = cf(snippet, lineOffset, argOverride) match {
31+
case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if debug =>
32+
val s = sequence.BasedSequence.EmptyBasedSequence()
33+
.append(wrapped)
34+
.append(sequence.BasedSequence.EOL)
35+
val content = mdu.BlockContent()
36+
content.add(s, 0)
37+
node.setContent(content)
38+
result
39+
case result => result
40+
}
41+
42+
node.insertBefore(new ExtendedFencedCodeBlock(node, snippetCompilationResult))
43+
node.unlink()
44+
}
45+
46+
root
47+
}

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ package snippets
33

44
import dotty.tools.scaladoc.DocContext
55
import java.nio.file.Paths
6+
import java.io.File
67

7-
class SnippetChecker()(using ctx: DocContext):
8+
class SnippetChecker(val classpath: String, val tastyDirs: Seq[File]):
89
private val sep = System.getProperty("path.separator")
9-
private val cp = System.getProperty("java.class.path") + sep +
10-
Paths.get(ctx.args.classpath).toAbsolutePath + sep +
11-
ctx.args.tastyDirs.map(_.getAbsolutePath()).mkString(sep)
10+
private val cp = List(
11+
System.getProperty("java.class.path"),
12+
Paths.get(classpath).toAbsolutePath,
13+
tastyDirs.map(_.getAbsolutePath()).mkString(sep)
14+
).mkString(sep)
15+
1216
private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp)
1317

1418
def checkSnippet(

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,16 @@ class SnippetCompiler(
4242
private def nullableMessage(msgOrNull: String): String =
4343
if (msgOrNull == null) "" else msgOrNull
4444

45-
private def createReportMessage(diagnostics: Seq[Diagnostic], line: Int, column: Int): Seq[SnippetCompilerMessage] = {
45+
private def createReportMessage(wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg, diagnostics: Seq[Diagnostic]): Seq[SnippetCompilerMessage] = {
46+
val line = wrappedSnippet.lineOffset
47+
val column = wrappedSnippet.columnOffset
48+
val lineBoilerplate = wrappedSnippet.lineBoilerplate
4649
val infos = diagnostics.toSeq.sortBy(_.pos.source.path)
4750
val errorMessages = infos.map {
4851
case diagnostic if diagnostic.position.isPresent =>
4952
val diagPos = diagnostic.position.get
5053
val pos = Some(
51-
Position(diagPos.line + line, diagPos.column + column, diagPos.lineContent, diagPos.line)
54+
Position(diagPos.line + line, diagPos.column + column, diagPos.lineContent, if arg.debug then diagPos.line else diagPos.line - lineBoilerplate)
5255
)
5356
val msg = nullableMessage(diagnostic.message)
5457
val level = MessageLevel.fromOrdinal(diagnostic.level)
@@ -85,7 +88,7 @@ class SnippetCompiler(
8588
run.compileFromStrings(List(wrappedSnippet.snippet))
8689

8790
val messages =
88-
createReportMessage(context.reporter.pendingMessages(using context), wrappedSnippet.lineOffset, wrappedSnippet.columnOffset) ++
91+
createReportMessage(wrappedSnippet, arg, context.reporter.pendingMessages(using context)) ++
8992
additionalMessages(wrappedSnippet, arg, context)
9093

9194
val t = Option.when(!context.reporter.hasErrors)(target)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ sealed trait SCFlags(val flagName: String)
1111
object SCFlags:
1212
case object Compile extends SCFlags("compile")
1313
case object NoCompile extends SCFlags("nocompile")
14-
case object Fail extends SCFlags("failing")
14+
case object Fail extends SCFlags("fail")
1515

1616
def values: Seq[SCFlags] = Seq(Compile, NoCompile, Fail)
1717

@@ -42,7 +42,7 @@ object SnippetCompilerArgs:
4242
|Available flags:
4343
|compile - Enables snippet checking.
4444
|nocompile - Disables snippet checking.
45-
|failing - Enables snippet checking, asserts that snippet doesn't compile.
45+
|fail - Enables snippet checking, asserts that snippet doesn't compile.
4646
|
4747
""".stripMargin
4848

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ package snippets
44
import java.io.ByteArrayOutputStream
55
import java.io.PrintStream
66

7-
case class WrappedSnippet(snippet: String, lineOffset: Int, columnOffset: Int)
7+
case class WrappedSnippet(snippet: String, lineOffset: Int, columnOffset: Int, lineBoilerplate: Int, columnBoilerplate: Int)
88

99
object WrappedSnippet:
10-
private val lineOffset = 2
11-
private val columnOffset = 2
10+
private val lineBoilerplate = 2
11+
private val columnBoilerplate = 2
1212

1313
def apply(str: String): WrappedSnippet =
1414
val baos = new ByteArrayOutputStream()
@@ -17,7 +17,7 @@ object WrappedSnippet:
1717
ps.println("object Snippet {")
1818
str.split('\n').foreach(ps.printlnWithIndent(2, _))
1919
ps.println("}")
20-
WrappedSnippet(baos.toString, lineOffset, columnOffset)
20+
WrappedSnippet(baos.toString, 0, 0, lineBoilerplate, columnBoilerplate)
2121

2222
def apply(
2323
str: String,
@@ -35,7 +35,7 @@ object WrappedSnippet:
3535
ps.println(s"trait Snippet${classGenerics.getOrElse("")} { ${className.fold("")(cn => s"self: $cn =>")}")
3636
str.split('\n').foreach(ps.printlnWithIndent(2, _))
3737
ps.println("}")
38-
WrappedSnippet(baos.toString, lineOffset, columnOffset)
38+
WrappedSnippet(baos.toString, lineOffset, columnOffset, lineBoilerplate, columnBoilerplate)
3939

4040
extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) =
4141
ps.println((" " * indent) + str)

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import dotty.tools.scaladoc.tasty.comments.Comment
99
trait ScaladocSupport { self: TastyParser =>
1010
import qctx.reflect._
1111

12-
private val snippetChecker: snippets.SnippetChecker = snippets.SnippetChecker()
13-
1412
def parseCommentString(comment: String, sym: Symbol, pos: Option[Position]): Comment =
1513
val preparsed = comments.Preparser.preparse(comments.Cleaner.clean(comment))
1614

@@ -29,9 +27,9 @@ trait ScaladocSupport { self: TastyParser =>
2927

3028
val parser = commentSyntax match {
3129
case CommentSyntax.Wiki =>
32-
comments.WikiCommentParser(comments.Repr(qctx)(sym), snippetChecker)
30+
comments.WikiCommentParser(comments.Repr(qctx)(sym))
3331
case CommentSyntax.Markdown =>
34-
comments.MarkdownCommentParser(comments.Repr(qctx)(sym), snippetChecker)
32+
comments.MarkdownCommentParser(comments.Repr(qctx)(sym))
3533
}
3634
parser.parse(preparsed)
3735

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

Lines changed: 9 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ case class PreparsedComment(
7171

7272
case class DokkaCommentBody(summary: Option[DocPart], body: DocPart)
7373

74-
abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecker)(using dctx: DocContext) {
74+
abstract class MarkupConversion[T](val repr: Repr)(using dctx: DocContext) {
7575
protected def stringToMarkup(str: String): T
7676
protected def markupToDokka(t: T): DocPart
7777
protected def markupToString(t: T): String
@@ -80,6 +80,8 @@ abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecke
8080
protected def filterEmpty(xs: SortedMap[String, String]): SortedMap[String, T]
8181
protected def processSnippets(t: T): T
8282

83+
lazy val snippetChecker = dctx.snippetChecker
84+
8385
val qctx: repr.qctx.type = if repr == null then null else repr.qctx // TODO why we do need null?
8486
val owner: qctx.reflect.Symbol =
8587
if repr == null then null.asInstanceOf[qctx.reflect.Symbol] else repr.sym
@@ -220,8 +222,8 @@ abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecke
220222
)
221223
}
222224

223-
class MarkdownCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using dctx: DocContext)
224-
extends MarkupConversion[mdu.Node](repr, snippetChecker) {
225+
class MarkdownCommentParser(repr: Repr)(using dctx: DocContext)
226+
extends MarkupConversion[mdu.Node](repr) {
225227

226228
def stringToMarkup(str: String) =
227229
MarkdownParser.parseToMarkdown(str, markdown.DocFlexmarkParser(resolveLink))
@@ -247,56 +249,12 @@ class MarkdownCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using dc
247249
.filterNot { case (_, v) => v.isEmpty }
248250
.mapValues(stringToMarkup).to(SortedMap)
249251

250-
def processSnippets(root: mdu.Node): mdu.Node = {
251-
val nodes = root.getDescendants().asScala.collect {
252-
case fcb: mda.FencedCodeBlock => fcb
253-
}.toList
254-
if nodes.nonEmpty then {
255-
val checkingFunc: SnippetChecker.SnippetCheckingFunc = snippetCheckingFunc(owner)
256-
nodes.foreach { node =>
257-
val snippet = node.getContentChars.toString
258-
val lineOffset = node.getStartLineNumber
259-
val info = node.getInfo.toString
260-
val argOverride =
261-
info.split(" ")
262-
.find(_.startsWith("sc:"))
263-
.map(_.stripPrefix("sc:"))
264-
.map(snippets.SCFlagsParser.parse)
265-
.flatMap(_.toOption)
266-
val snippetCompilationResult = checkingFunc(snippet, lineOffset, argOverride) match {
267-
case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if dctx.snippetCompilerArgs.debug =>
268-
val s = sequence.BasedSequence.EmptyBasedSequence()
269-
.append(wrapped)
270-
.append(sequence.BasedSequence.EOL)
271-
val content = mdu.BlockContent()
272-
content.add(s, 0)
273-
node.setContent(content)
274-
result
275-
case result =>
276-
result.map { r =>
277-
r.copy(
278-
messages = r.messages.map { m =>
279-
m.copy(
280-
position = m.position.map { p =>
281-
p.copy(
282-
relativeLine = p.relativeLine - lineOffset
283-
)
284-
}
285-
)
286-
}
287-
)
288-
}
289-
}
290-
node.insertBefore(new ExtendedFencedCodeBlock(node, snippetCompilationResult))
291-
node.unlink()
292-
}
293-
}
294-
root
295-
}
252+
def processSnippets(root: mdu.Node): mdu.Node =
253+
FlexmarkSnippetProcessor.processSnippets(root, dctx.snippetCompilerArgs.debug, snippetCheckingFunc(owner))
296254
}
297255

298-
class WikiCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using DocContext)
299-
extends MarkupConversion[wiki.Body](repr, snippetChecker):
256+
class WikiCommentParser(repr: Repr)(using DocContext)
257+
extends MarkupConversion[wiki.Body](repr):
300258

301259
def stringToMarkup(str: String) = wiki.Parser(str, resolverLink).document()
302260

0 commit comments

Comments
 (0)