Skip to content

Commit d228d36

Browse files
committed
Add arguments to control snippet compiler.
1 parent f250e6a commit d228d36

File tree

10 files changed

+171
-40
lines changed

10 files changed

+171
-40
lines changed

project/Build.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,11 @@ object Build {
16141614
val stdLibRoot = projectRoot.relativize(managedSources.toPath.normalize())
16151615
val docRootFile = stdLibRoot.resolve("rootdoc.txt")
16161616

1617+
val dottyManagedSources =
1618+
(`stdlib-bootstrapped`/Compile/sourceManaged).value / "dotty-library-src"
1619+
1620+
val dottyLibRoot = projectRoot.relativize(dottyManagedSources.toPath.normalize())
1621+
16171622
if (dottyJars.isEmpty) Def.task { streams.value.log.error("Dotty lib wasn't found") }
16181623
else Def.task{
16191624
IO.write(dest / "versions" / "latest-nightly-base", majorVersion)
@@ -1644,6 +1649,9 @@ object Build {
16441649
s"$stdLibRoot=github://scala/scala/v${stdlibVersion(Bootstrapped)}#src/library," +
16451650
s"docs=github://lampepfl/dotty/master#docs",
16461651
"-doc-root-content", docRootFile.toString,
1652+
"-snippet-compiler-args:" +
1653+
s"$dottyLibRoot/scala/quoted=nocompile," +
1654+
s"$dottyLibRoot=compile",
16471655
"-Ydocument-synthetic-types"
16481656
)
16491657
))
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package snippetCompiler
2+
3+
/**
4+
* ```scala sc:compile
5+
* def a = 2
6+
* a
7+
* ```
8+
*
9+
* ```scala sc:nocompile
10+
* def a = 3
11+
* a()
12+
* ```
13+
*/
14+
class A { }

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ case class NavigationNode(name: String, dri: DRI, nested: Seq[NavigationNode])
7070

7171
case class DocContext(args: Scaladoc.Args, compilerContext: CompilerContext):
7272
lazy val sourceLinks = SourceLinks.load(args.sourceLinks, args.revision)(using compilerContext)
73+
74+
lazy val snippetCompilerArgs = snippets.SnippetCompilerArgs.load(args.snippetCompilerArgs)(using compilerContext)
75+
7376
lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext(
7477
File(path).getAbsoluteFile(),
7578
args,

scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ object Scaladoc:
4444
regexesToSkip: List[String] = Nil,
4545
rootDocPath: Option[String] = None,
4646
documentSyntheticTypes: Boolean = false,
47+
snippetCompilerArgs: List[String] = Nil
4748
)
4849

4950
def run(args: Array[String], rootContext: CompilerContext): Reporter =
@@ -60,7 +61,7 @@ object Scaladoc:
6061
val tastyFiles = parsedArgs.tastyFiles ++ parsedArgs.tastyDirs.flatMap(listTastyFiles)
6162

6263
if !ctx.reporter.hasErrors then
63-
val updatedArgs = parsedArgs.copy(tastyDirs = Nil, tastyFiles = tastyFiles)
64+
val updatedArgs = parsedArgs.copy(tastyDirs = parsedArgs.tastyDirs, tastyFiles = tastyFiles)
6465

6566
if (parsedArgs.output.exists()) util.IO.delete(parsedArgs.output)
6667

@@ -173,7 +174,8 @@ object Scaladoc:
173174
skipById.get ++ deprecatedSkipPackages.get,
174175
skipByRegex.get,
175176
docRootContent.nonDefault,
176-
YdocumentSyntheticTypes.get
177+
YdocumentSyntheticTypes.get,
178+
snippetCompilerArgs.get
177179
)
178180
(Some(docArgs), newContext)
179181
}

scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,8 @@ class ScaladocSettings extends SettingGroup with CommonScalaSettings:
6060
val YdocumentSyntheticTypes: Setting[Boolean] =
6161
BooleanSetting("-Ydocument-synthetic-types", "Documents intrinsic types e. g. Any, Nothing. Setting is useful only for stdlib", false)
6262

63+
val snippetCompilerArgs: Setting[List[String]] =
64+
MultiStringSetting("-snippet-compiler-args", "snippet-compiler-args", snippets.SnippetCompilerArgs.usage)
65+
6366
def scaladocSpecificSettings: Set[Setting[_]] =
64-
Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent)
67+
Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompilerArgs)

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,9 @@ extension (s: Signature)
234234

235235
case class TastyMemberSource(val path: java.nio.file.Path, val lineNumber: Int)
236236

237-
case class SnippetCompilerData(val packageName: String, val classType: Option[String], val classGenerics: Option[String], val imports: List[String])
237+
case class SnippetCompilerData(
238+
val packageName: String,
239+
val classType: Option[String],
240+
val classGenerics: Option[String],
241+
val imports: List[String]
242+
)

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@ import dotty.tools.scaladoc.tasty.comments.wiki.WikiDocElement
88
import dotty.tools.scaladoc.tasty.comments.markdown.DocFlexmarkRenderer
99
import dotty.tools.scaladoc.snippets._
1010

11-
class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using DocContext):
11+
class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using ctx: DocContext):
1212

13-
private val snippetCheckingFunc: Member => String => Unit =
13+
private val snippetCheckingFunc: Member => (String, Option[SnippetCompilerArg]) => Unit =
1414
(m: Member) => {
15-
(str: String) => {
16-
snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData)) match {
17-
case r @ SnippetCompilationResult(None, _) =>
18-
println(s"In member ${m.name}:")
19-
println(r.getSummary)
20-
case _ =>
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 _ =>
25+
}
2126
}
2227
}
2328
}
@@ -62,7 +67,7 @@ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChe
6267
case 6 => h6(content)
6368
case Paragraph(text) => p(renderElement(text))
6469
case Code(data: String) =>
65-
snippetCheckingFunc(m)(data)
70+
snippetCheckingFunc(m)(data, None)
6671
pre(code(raw(data))) // TODO add classes
6772
case HorizontalRule => hr
6873

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

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,36 @@ package dotty.tools.scaladoc
22
package snippets
33

44
import dotty.tools.scaladoc.DocContext
5+
import java.nio.file.Paths
56

6-
class SnippetChecker(
7-
private val compiler: SnippetCompiler = SnippetCompiler(),
7+
class SnippetChecker()(using ctx: DocContext):
8+
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)
12+
private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp)
813
private val wrapper: SnippetWrapper = SnippetWrapper()
9-
):
1014
var warningsCount = 0
1115
var errorsCount = 0
1216

13-
def checkSnippet(snippet: String, data: Option[SnippetCompilerData]): SnippetCompilationResult = {
14-
val wrapped = wrapper.wrap(
15-
snippet,
16-
data.map(_.packageName),
17-
data.flatMap(_.classType),
18-
data.flatMap(_.classGenerics),
19-
data.map(_.imports).getOrElse(Nil)
20-
)
21-
val res = compiler.compile(wrapped)
22-
if !res.messages.filter(_.level == MessageLevel.Error).isEmpty then errorsCount = errorsCount + 1
23-
if !res.messages.filter(_.level == MessageLevel.Warning).isEmpty then warningsCount = warningsCount + 1
24-
res
17+
def checkSnippet(
18+
snippet: String,
19+
data: Option[SnippetCompilerData],
20+
arg: SnippetCompilerArg
21+
): Option[SnippetCompilationResult] = {
22+
if arg.is(SCFlags.Compile) then
23+
val wrapped = wrapper.wrap(
24+
snippet,
25+
data.map(_.packageName),
26+
data.flatMap(_.classType),
27+
data.flatMap(_.classGenerics),
28+
data.map(_.imports).getOrElse(Nil)
29+
)
30+
val res = compiler.compile(wrapped)
31+
if !res.messages.filter(_.level == MessageLevel.Error).isEmpty then errorsCount = errorsCount + 1
32+
if !res.messages.filter(_.level == MessageLevel.Warning).isEmpty then warningsCount = warningsCount + 1
33+
Some(res)
34+
else None
2535
}
2636

2737
def summary: String = s"""
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package dotty.tools.scaladoc
2+
package snippets
3+
4+
case class SnippetCompilerArg(flags: Set[SCFlags]):
5+
def is(flag: SCFlags): Boolean = flags.contains(flag)
6+
7+
object SnippetCompilerArg:
8+
def default: SnippetCompilerArg = SnippetCompilerArg(
9+
Set(SCFlags.NoCompile)
10+
)
11+
12+
enum SCFlags(val flagName: String, val forbiddenFlags: Set[SCFlags]):
13+
case Compile extends SCFlags("compile", Set())
14+
case NoCompile extends SCFlags("nocompile", Set())
15+
16+
case class SnippetCompilerArgs(scArgs: PathBased[SnippetCompilerArg]):
17+
def get(member: Member): Option[SnippetCompilerArg] = member.sources.flatMap(s => scArgs.get(s.path).map(_.elem))
18+
19+
object SnippetCompilerArgs:
20+
val usage =
21+
"""
22+
|Snippet compiler arguments provide a way to configure snippet checking.
23+
|
24+
|This setting accept list of arguments in format:
25+
|arg := [path=]flag{&flag}
26+
|where path is a prefix of source paths to members to which argument should be set.
27+
|
28+
|If path is not present, argument will be used as default.
29+
|
30+
|Available flags:
31+
|compile - Enables snippet checking. Cannot be used with nocompile.
32+
|nocompile - Disables snippet checking. Cannot be used with compile.
33+
|
34+
""".stripMargin
35+
36+
def load(args: List[String])(using CompilerContext): SnippetCompilerArgs = {
37+
PathBased.parse[SnippetCompilerArg](args)(using SnippetCompilerArgParser) match {
38+
case PathBased.ParsingResult(errors, res) =>
39+
if errors.nonEmpty then report.warning(
40+
s"""Got following errors during snippet compiler args parsing:
41+
|$errors
42+
|
43+
|${usage}
44+
|""".stripMargin
45+
)
46+
SnippetCompilerArgs(res)
47+
}
48+
}
49+
50+
object SnippetCompilerArgParser extends ArgParser[SnippetCompilerArg]:
51+
def parse(s: String): Either[String, SnippetCompilerArg] = {
52+
val flagStrings = s.split("&")
53+
val (parsed, errors) = flagStrings.map(flag => SCFlags.values.find(_.flagName == flag).fold(
54+
Left(s"$flag: No such flag found.")
55+
)(
56+
Right(_)
57+
)
58+
).partition(_.isRight)
59+
60+
val (flags, errors2) = parsed.collect {
61+
case Right(flag) => flag
62+
} match {
63+
case list => list.map(f =>
64+
list.find(elem => f.forbiddenFlags.contains(elem)) match {
65+
case Some(forbiddenElem) => Left(s"${f.flagName}: Cannot be used with flag: ${forbiddenElem.flagName}")
66+
case None => Right(f)
67+
}
68+
).partition(_.isRight)
69+
}
70+
71+
val checkedFlags = flags.collect {
72+
case Right(flag) => flag
73+
}.toSet
74+
75+
val allErrors = (errors ++ errors2).collect {
76+
case Left(error) => error
77+
}.toList
78+
79+
if !allErrors.isEmpty then Left(allErrors.mkString("\n")) else Right(SnippetCompilerArg(checkedFlags))
80+
}

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

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,21 @@ object DocFlexmarkParser {
4545
}
4646
}
4747

48-
case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String) => Unit)
48+
case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String, Option[snippets.SnippetCompilerArg]) => Unit)
4949
extends HtmlRenderer.HtmlRendererExtension:
5050

5151
def rendererOptions(opt: MutableDataHolder): Unit = () // noop
5252

53-
object CodeHandler extends CustomNodeRenderer[ast.Code]:
54-
override def render(node: ast.Code, c: NodeRendererContext, html: HtmlWriter): Unit =
55-
snippetCheckingFunc(node.getText.toString)
56-
c.delegateRender()
57-
58-
object CodeBlockHandler extends CustomNodeRenderer[ast.CodeBlock]:
59-
override def render(node: ast.CodeBlock, c: NodeRendererContext, html: HtmlWriter): Unit =
60-
snippetCheckingFunc(node.getContentChars.toString)
53+
object FencedCodeBlockHandler extends CustomNodeRenderer[ast.FencedCodeBlock]:
54+
override def render(node: ast.FencedCodeBlock, c: NodeRendererContext, html: HtmlWriter): Unit =
55+
val info = node.getInfo.toString
56+
val argOverride =
57+
info.split(" ")
58+
.find(_.startsWith("sc:"))
59+
.map(_.stripPrefix("sc:"))
60+
.map(snippets.SnippetCompilerArgParser.parse)
61+
.flatMap(_.toOption)
62+
snippetCheckingFunc(node.getContentChars.toString, argOverride)
6163
c.delegateRender()
6264

6365
object Handler extends CustomNodeRenderer[DocLinkNode]:
@@ -68,8 +70,7 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetC
6870
override def getNodeRenderingHandlers: JSet[NodeRenderingHandler[_]] =
6971
JSet(
7072
new NodeRenderingHandler(classOf[DocLinkNode], Handler),
71-
new NodeRenderingHandler(classOf[ast.Code], CodeHandler),
72-
new NodeRenderingHandler(classOf[ast.CodeBlock], CodeBlockHandler)
73+
new NodeRenderingHandler(classOf[ast.FencedCodeBlock], FencedCodeBlockHandler)
7374
)
7475

7576
object Factory extends NodeRendererFactory:
@@ -79,6 +80,6 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetC
7980
htmlRendererBuilder.nodeRendererFactory(Factory)
8081

8182
object DocFlexmarkRenderer:
82-
def render(node: Node)(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String) => Unit) =
83+
def render(node: Node)(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String, Option[snippets.SnippetCompilerArg]) => Unit) =
8384
val opts = MarkdownParser.mkMarkdownOptions(Seq(DocFlexmarkRenderer(renderLink, snippetCheckingFunc)))
8485
HtmlRenderer.builder(opts).build().render(node)

0 commit comments

Comments
 (0)