From f7c8f52f431cf5b6dffdcf5ea93d6fa0fce65b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Wed, 3 Mar 2021 11:36:17 +0100 Subject: [PATCH 01/28] Snippet compiler PoC --- .../scaladoc/snippets/SnippetChecker.scala | 13 ++++ .../snippets/SnippetCompilationResult.scala | 9 +++ .../scaladoc/snippets/SnippetCompiler.scala | 77 +++++++++++++++++++ .../scaladoc/snippets/SnippetWrapper.scala | 14 ++++ .../snippets/SnippetCompilerTest.scala | 50 ++++++++++++ 5 files changed, 163 insertions(+) create mode 100644 scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala create mode 100644 scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala create mode 100644 scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala create mode 100644 scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala create mode 100644 scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala new file mode 100644 index 000000000000..eadc3f0205ab --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -0,0 +1,13 @@ +package dotty.tools.scaladoc +package snippets + +import dotty.tools.scaladoc.DocContext + +class SnippetChecker( + private val compiler: SnippetCompiler = SnippetCompiler(), + private val wrapper: SnippetWrapper = SnippetWrapper() +): + def checkSnippet(snippet: String): SnippetCompilationResult = { + val wrapped = wrapper.wrap(snippet) + compiler.compile(wrapped) + } \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala new file mode 100644 index 000000000000..ca713d5c965e --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala @@ -0,0 +1,9 @@ +package dotty.tools.scaladoc +package snippets + +import dotty.tools.io.{ AbstractFile } + +case class SnippetCompilerMessage(line: Int, column: Int, sourceLine: String, message: String) + +case class SnippetCompilationResult(result: Option[AbstractFile], messages: Seq[SnippetCompilerMessage]): + def getSummary: String = messages.map(m => s"At ${m.line}:${m.column}:\n${m.sourceLine}Compiler: ${m.message}").mkString("\n") \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala new file mode 100644 index 000000000000..783c29f4987d --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -0,0 +1,77 @@ +package dotty.tools.scaladoc +package snippets + +import dotty.tools.io.{AbstractFile, VirtualDirectory} +import dotty.tools.dotc.interactive.InteractiveDriver +import dotty.tools.dotc.interactive.Interactive +import dotty.tools.dotc.interactive.InteractiveCompiler +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.config.Settings.Setting._ +import dotty.tools.dotc.interfaces.SourcePosition +import dotty.tools.dotc.ast.Trees.Tree +import dotty.tools.dotc.interfaces.{SourceFile => ISourceFile} +import dotty.tools.dotc.reporting.{ Diagnostic, StoreReporter } +import dotty.tools.dotc.parsing.Parsers.Parser +import dotty.tools.dotc.{ Compiler, Run } +import dotty.tools.io.{AbstractFile, VirtualDirectory} +import dotty.tools.repl.AbstractFileClassLoader +import dotty.tools.dotc.util.SourceFile + +class SnippetCompiler( + classpath: String = System.getProperty("java.class.path"), //Probably needs to be done better + val scalacOptions: String = "", + target: AbstractFile = new VirtualDirectory("(memory)") +): + + private def newDriver: InteractiveDriver = { + val defaultFlags = + List("-color:never", "-unchecked", "-deprecation", "-Ximport-suggestion-timeout", "0") + val options = scalacOptions.split("\\s+").toList + val settings = + options ::: defaultFlags ::: "-classpath" :: classpath :: Nil + new InteractiveDriver(settings) + } + + private val driver = newDriver + + private val scala3Compiler = new Compiler + + private def newRun(using ctx: Context): Run = scala3Compiler.newRun + + private def nullableMessage(msgOrNull: String): String = + if (msgOrNull == null) "" else msgOrNull + + private def createReportMessage(diagnostics: Seq[Diagnostic]): Seq[SnippetCompilerMessage] = { + val infos = diagnostics.toSeq.sortBy(_.pos.source.path) + val errorMessages = infos.map { + case diagnostic if diagnostic.position.isPresent => + val pos = diagnostic.position.get + val msg = nullableMessage(diagnostic.message) + SnippetCompilerMessage(pos.line, pos.column, pos.lineContent, msg) + case d => SnippetCompilerMessage(-1, -1, "", nullableMessage(d.message)) + } + errorMessages + } + + def compile( + snippets: List[String] + ): SnippetCompilationResult = { + val context = driver.currentCtx.fresh + .setSetting( + driver.currentCtx.settings.outputDir, + target + ) + .setReporter(new StoreReporter) + val run = newRun(using context) + run.compileFromStrings(snippets) + //Currently no warnings because of reporter implementation + val messages = createReportMessage(context.reporter.allErrors) + val targetIfSuccessful = Option.when(!context.reporter.hasErrors)(target) + SnippetCompilationResult(targetIfSuccessful, messages) + } + + def compile( + snippet: String + ): SnippetCompilationResult = compile(List(snippet)) + + diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala new file mode 100644 index 000000000000..f50191ec6bbe --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala @@ -0,0 +1,14 @@ +package dotty.tools.scaladoc +package snippets + +class SnippetWrapper: + def wrap(str: String): String = s""" + |package snippets + |object Snippet { + | $str + |} + |""".stripMargin + +object SnippetWrapper: + val lineOffset = 2 + val columnOffset = 2 \ No newline at end of file diff --git a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala new file mode 100644 index 000000000000..e099635b5c19 --- /dev/null +++ b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala @@ -0,0 +1,50 @@ +package dotty.tools.scaladoc +package snippets + +import org.junit.Test +import org.junit.Assert._ +import dotty.tools.io.{AbstractFile, VirtualDirectory} + +class SnippetCompilerTest { + val compiler = SnippetCompiler() + def runTest(str: String) = compiler.compile(str) + + def runTest(str: List[String]) = compiler.compile(str) + + private def assertSuccessfulCompilation(res: SnippetCompilationResult): Unit = res match { + case SnippetCompilationResult(Some(target), _) => assert(true) + case r @ SnippetCompilationResult(None, _) => assert(false, r.getSummary) + } + + private def assertFailedCompilation(res: SnippetCompilationResult): Unit = res match { + case SnippetCompilationResult(Some(target), _) => assert(false, "Expected compilation failure") + case r @ SnippetCompilationResult(None, _) => assert(true) + } + + def assertSuccessfulCompilation(str: String): Unit = assertSuccessfulCompilation(runTest(str)) + + def assertFailedCompilation(str: String): Unit = assertFailedCompilation(runTest(str)) + + def assertSuccessfulCompilation(str: List[String]): Unit = assertSuccessfulCompilation(runTest(str)) + + def assertFailedCompilation(str: List[String]): Unit = assertFailedCompilation(runTest(str)) + + + @Test + def snippetCompilerTest: Unit = { + val simpleCorrectSnippet = s""" + |package asd + |class A: + | val b: String = "asd" + |""".stripMargin + + val simpleIncorrectSnippet = s""" + |package asd + |class A: + | val b: String + |""".stripMargin + assertSuccessfulCompilation(simpleCorrectSnippet) + assertFailedCompilation(simpleIncorrectSnippet) + assertFailedCompilation(List(simpleCorrectSnippet, simpleCorrectSnippet)) + } +} \ No newline at end of file From 80a401451557f676d351a3ac3beca2218f557958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 8 Mar 2021 09:41:53 +0100 Subject: [PATCH 02/28] Reporter fix. Message levels --- .../tools/dotc/reporting/StoreReporter.scala | 2 +- .../snippets/SnippetCompilationResult.scala | 11 +++++++++-- .../scaladoc/snippets/SnippetCompiler.scala | 11 +++++++---- .../snippets/SnippetCompilerTest.scala | 19 +++++++++++++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala index 8b6cd6ea5a0d..d3c334599666 100644 --- a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -37,7 +37,7 @@ class StoreReporter(outer: Reporter = Reporter.NoReporter) extends Reporter { if (infos != null) try infos.toList finally infos = null else Nil - override def pendingMessages(using Context): List[Diagnostic] = infos.toList + override def pendingMessages(using Context): List[Diagnostic] = if (infos != null) infos.toList else Nil override def errorsReported: Boolean = hasErrors || (outer != null && outer.errorsReported) } diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala index ca713d5c965e..2083b880fe07 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala @@ -3,7 +3,14 @@ package snippets import dotty.tools.io.{ AbstractFile } -case class SnippetCompilerMessage(line: Int, column: Int, sourceLine: String, message: String) +case class SnippetCompilerMessage(line: Int, column: Int, sourceLine: String, message: String, level: MessageLevel) case class SnippetCompilationResult(result: Option[AbstractFile], messages: Seq[SnippetCompilerMessage]): - def getSummary: String = messages.map(m => s"At ${m.line}:${m.column}:\n${m.sourceLine}Compiler: ${m.message}").mkString("\n") \ No newline at end of file + def getSummary: String = messages.map(m => s"At ${m.line}:${m.column}:\n${m.sourceLine}${m.level.text}: ${m.message}").mkString("\n") + + +enum MessageLevel(val text: String): + case Info extends MessageLevel("Info") + case Warning extends MessageLevel("Warning") + case Error extends MessageLevel("Error") + diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index 783c29f4987d..1dad4af1d88a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -16,6 +16,7 @@ import dotty.tools.dotc.{ Compiler, Run } import dotty.tools.io.{AbstractFile, VirtualDirectory} import dotty.tools.repl.AbstractFileClassLoader import dotty.tools.dotc.util.SourceFile +import dotty.tools.dotc.interfaces.Diagnostic._ class SnippetCompiler( classpath: String = System.getProperty("java.class.path"), //Probably needs to be done better @@ -47,8 +48,11 @@ class SnippetCompiler( case diagnostic if diagnostic.position.isPresent => val pos = diagnostic.position.get val msg = nullableMessage(diagnostic.message) - SnippetCompilerMessage(pos.line, pos.column, pos.lineContent, msg) - case d => SnippetCompilerMessage(-1, -1, "", nullableMessage(d.message)) + val level = MessageLevel.fromOrdinal(diagnostic.level) + SnippetCompilerMessage(pos.line, pos.column, pos.lineContent, msg, level) + case d => + val level = MessageLevel.fromOrdinal(d.level) + SnippetCompilerMessage(-1, -1, "", nullableMessage(d.message), level) } errorMessages } @@ -64,8 +68,7 @@ class SnippetCompiler( .setReporter(new StoreReporter) val run = newRun(using context) run.compileFromStrings(snippets) - //Currently no warnings because of reporter implementation - val messages = createReportMessage(context.reporter.allErrors) + val messages = createReportMessage(context.reporter.pendingMessages(using context)) val targetIfSuccessful = Option.when(!context.reporter.hasErrors)(target) SnippetCompilationResult(targetIfSuccessful, messages) } diff --git a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala index e099635b5c19..19a3e54e465c 100644 --- a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala @@ -29,6 +29,15 @@ class SnippetCompilerTest { def assertFailedCompilation(str: List[String]): Unit = assertFailedCompilation(runTest(str)) + def assertMessageLevelPresent(str: String, level: MessageLevel): Unit = assertMessageLevelPresent(runTest(str), level) + + def assertMessageLevelPresent(res: SnippetCompilationResult, level: MessageLevel): Unit = res match { + case r @ SnippetCompilationResult(_, messages) => assertTrue( + s"Expected message with level: ${level.text}. Got result ${r.getSummary}", + messages.exists(_.level == level) + ) + } + @Test def snippetCompilerTest: Unit = { @@ -43,8 +52,18 @@ class SnippetCompilerTest { |class A: | val b: String |""".stripMargin + val warningSnippet = s""" + |package asd + |class A: + | val a: Int = try { + | 5 + | } + |""".stripMargin assertSuccessfulCompilation(simpleCorrectSnippet) assertFailedCompilation(simpleIncorrectSnippet) assertFailedCompilation(List(simpleCorrectSnippet, simpleCorrectSnippet)) + assertMessageLevelPresent(simpleIncorrectSnippet, MessageLevel.Error) + assertMessageLevelPresent(warningSnippet, MessageLevel.Warning) + //No test for Info } } \ No newline at end of file From 825f6c3a5a1a7712decd59963118fc914419b7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 8 Mar 2021 11:49:02 +0100 Subject: [PATCH 03/28] Fix snippet wrapper. Integrate snippet checker with Wiki and Markdown renderers --- .../scaladoc/renderers/DocRenderer.scala | 33 +++++++++++++++---- .../scaladoc/renderers/MemberRenderer.scala | 16 ++++----- .../scaladoc/snippets/SnippetWrapper.scala | 19 +++++++---- .../markdown/DocFlexmarkExtension.scala | 27 +++++++++++---- 4 files changed, 68 insertions(+), 27 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala index 6f52abb0a61b..fe0f6dc45d08 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala @@ -6,21 +6,38 @@ import util.HTML._ import com.vladsch.flexmark.util.ast.{Node => MdNode} import dotty.tools.scaladoc.tasty.comments.wiki.WikiDocElement import dotty.tools.scaladoc.tasty.comments.markdown.DocFlexmarkRenderer +import dotty.tools.scaladoc.snippets._ class DocRender(signatureRenderer: SignatureRenderer)(using DocContext): - def renderDocPart(doc: DocPart): AppliedTag = doc match + private val snippetChecker = SnippetChecker() + + private val snippetCheckingFunc: Member => String => Unit = + (m: Member) => { + (str: String) => { + snippetChecker.checkSnippet(str) match { + case r @ SnippetCompilationResult(None, _) => + println(s"In member ${m.name}:") + println(r.getSummary) + case _ => + } + } + } + + def renderDocPart(doc: DocPart)(using Member): AppliedTag = doc match case md: MdNode => renderMarkdown(md) case Nil => raw("") case Seq(elem: WikiDocElement) => renderElement(elem) case list: Seq[WikiDocElement @unchecked] => div(list.map(renderElement)) - private def renderMarkdown(el: MdNode): AppliedTag = - raw(DocFlexmarkRenderer.render(el)( (link,name) => - renderLink(link, default => text(if name.isEmpty then default else name)).toString + private def renderMarkdown(el: MdNode)(using m: Member): AppliedTag = + raw(DocFlexmarkRenderer.render(el)( + (link,name) => + renderLink(link, default => text(if name.isEmpty then default else name)).toString, + snippetCheckingFunc(m) )) - private def listItems(items: Seq[WikiDocElement]) = + private def listItems(items: Seq[WikiDocElement])(using m: Member) = items.map(i => li(renderElement(i))) private def notSupported(name: String, content: AppliedTag): AppliedTag = report.warning(s"Wiki syntax does not support $name in ${signatureRenderer.currentDri.location}") @@ -35,7 +52,7 @@ class DocRender(signatureRenderer: SignatureRenderer)(using DocContext): val tooltip = s"Problem linking $query: $msg" signatureRenderer.unresolvedLink(linkBody(query), titleAttr := tooltip) - private def renderElement(e: WikiDocElement): AppliedTag = e match + private def renderElement(e: WikiDocElement)(using m: Member): AppliedTag = e match case Title(text, level) => val content = renderElement(text) level match @@ -46,7 +63,9 @@ class DocRender(signatureRenderer: SignatureRenderer)(using DocContext): case 5 => h5(content) case 6 => h6(content) case Paragraph(text) => p(renderElement(text)) - case Code(data: String) => pre(code(raw(data))) // TODO add classes + case Code(data: String) => + snippetCheckingFunc(m)(data) + pre(code(raw(data))) // TODO add classes case HorizontalRule => hr case UnorderedList(items) => ul(listItems(items)) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala index 2e58af5c3da9..0677d148434f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala @@ -13,7 +13,7 @@ import translators._ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) extends DocRender(signatureRenderer): import signatureRenderer._ - def doc(m: Member): Seq[AppliedTag] = m.docs.fold(Nil)(d => Seq(renderDocPart(d.body))) + def doc(m: Member): Seq[AppliedTag] = m.docs.fold(Nil)(d => Seq(renderDocPart(d.body)(using m))) def tableRow(name: String, content: AppliedTag) = Seq(dt(name), dd(content)) @@ -37,14 +37,14 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext def nested(name: String, on: SortedMap[String, DocPart]): Seq[AppliedTag] = if on.isEmpty then Nil else tableRow(name, dl(cls := "attributes")( - on.map { case (name, value) => tableRow(name, renderDocPart(value))}.toList:_* + on.map { case (name, value) => tableRow(name, renderDocPart(value)(using m))}.toList:_* )) def list(name: String, on: List[DocPart]): Seq[AppliedTag] = - if on.isEmpty then Nil else tableRow(name, div(on.map(e => div(renderDocPart(e))))) + if on.isEmpty then Nil else tableRow(name, div(on.map(e => div(renderDocPart(e)(using m))))) def opt(name: String, on: Option[DocPart]): Seq[AppliedTag] = - if on.isEmpty then Nil else tableRow(name, renderDocPart(on.get)) + if on.isEmpty then Nil else tableRow(name, renderDocPart(on.get)(using m)) def authors(authors: List[DocPart]) = if summon[DocContext].args.includeAuthors then list("Authors", authors) else Nil @@ -89,14 +89,14 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext Seq( since.map(s => code("[Since version ", parameter(s), "] ")), message.map(m => parameter(m))) - ++ m.docs.map(_.deprecated.toSeq.map(renderDocPart)) + ++ m.docs.map(_.deprecated.toSeq.map(renderDocPart(_)(using m))) ).flatten Seq(dt("Deprecated"), dd(content:_*)) } def memberInfo(m: Member, withBrief: Boolean = false): Seq[AppliedTag] = val comment = m.docs - val bodyContents = m.docs.fold(Nil)(e => renderDocPart(e.body) :: Nil) + val bodyContents = m.docs.fold(Nil)(e => renderDocPart(e.body)(using m) :: Nil) Seq( Option.when(withBrief)(div(cls := "documentableBrief doc")(comment.flatMap(_.short).fold("")(renderDocPart))), @@ -251,8 +251,8 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext val rawGroups = membersInGroups.groupBy(_.docs.flatMap(_.group)).collect { case (Some(groupName), members) => ExpandedGroup( - names.get(groupName).fold(raw(groupName))(renderDocPart), - descriptions.get(groupName).fold(raw(""))(renderDocPart), + names.get(groupName).fold(raw(groupName))(renderDocPart(_)(using m)), + descriptions.get(groupName).fold(raw(""))(renderDocPart(_)(using m)), prios.getOrElse(groupName, 1000) ) -> members } diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala index f50191ec6bbe..51bed0840ccf 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala @@ -1,13 +1,20 @@ package dotty.tools.scaladoc package snippets +import java.io.ByteArrayOutputStream +import java.io.PrintStream + class SnippetWrapper: - def wrap(str: String): String = s""" - |package snippets - |object Snippet { - | $str - |} - |""".stripMargin + extension (ps: PrintStream) def printlnWithIndent(indent: Int, str: String) = + ps.println((" " * indent) + str) + def wrap(str: String): String = + val baos = new ByteArrayOutputStream() + val ps = new PrintStream(baos) + ps.println("package snippets") + ps.println("object Snippet {") + str.split('\n').foreach(ps.printlnWithIndent(2, _)) + ps.println("}") + baos.toString object SnippetWrapper: val lineOffset = 2 diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala index ad5e4ac20e2d..f1b3e3626684 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala @@ -10,7 +10,7 @@ import com.vladsch.flexmark.ext.wikilink.internal.WikiLinkLinkRefProcessor import com.vladsch.flexmark.util.ast._ import com.vladsch.flexmark.util.options._ import com.vladsch.flexmark.util.sequence.BasedSequence - +import com.vladsch.flexmark._ class DocLinkNode( val target: DocLink, @@ -45,17 +45,32 @@ object DocFlexmarkParser { } } -case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String) +case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String) => Unit) extends HtmlRenderer.HtmlRendererExtension: + def rendererOptions(opt: MutableDataHolder): Unit = () // noop + object CodeHandler extends CustomNodeRenderer[ast.Code]: + override def render(node: ast.Code, c: NodeRendererContext, html: HtmlWriter): Unit = + snippetCheckingFunc(node.getText.toString) + c.delegateRender() + + object CodeBlockHandler extends CustomNodeRenderer[ast.CodeBlock]: + override def render(node: ast.CodeBlock, c: NodeRendererContext, html: HtmlWriter): Unit = + snippetCheckingFunc(node.getContentChars.toString) + c.delegateRender() + object Handler extends CustomNodeRenderer[DocLinkNode]: override def render(node: DocLinkNode, c: NodeRendererContext, html: HtmlWriter): Unit = html.raw(renderLink(node.target, node.body)) object Render extends NodeRenderer: override def getNodeRenderingHandlers: JSet[NodeRenderingHandler[_]] = - JSet(new NodeRenderingHandler(classOf[DocLinkNode], Handler)) + JSet( + new NodeRenderingHandler(classOf[DocLinkNode], Handler), + new NodeRenderingHandler(classOf[ast.Code], CodeHandler), + new NodeRenderingHandler(classOf[ast.CodeBlock], CodeBlockHandler) + ) object Factory extends NodeRendererFactory: override def create(options: DataHolder): NodeRenderer = Render @@ -64,6 +79,6 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String) htmlRendererBuilder.nodeRendererFactory(Factory) object DocFlexmarkRenderer: - def render(node: Node)(renderLink: (DocLink, String) => String) = - val opts = MarkdownParser.mkMarkdownOptions(Seq(DocFlexmarkRenderer(renderLink))) - HtmlRenderer.builder(opts).escapeHtml(true).build().render(node) + def render(node: Node)(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String) => Unit) = + val opts = MarkdownParser.mkMarkdownOptions(Seq(DocFlexmarkRenderer(renderLink, snippetCheckingFunc))) + HtmlRenderer.builder(opts).build().render(node) From 2c7c92d0cd922584f464ac4d2a54317dd2f1617e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Thu, 18 Mar 2021 11:12:26 +0100 Subject: [PATCH 04/28] Further implementation of snippet compiler. Add package name and self type to pass context --- scaladoc/src/dotty/tools/scaladoc/api.scala | 2 + .../scaladoc/renderers/DocRenderer.scala | 16 ++++---- .../scaladoc/renderers/HtmlRenderer.scala | 6 ++- .../scaladoc/renderers/MemberRenderer.scala | 3 +- .../scaladoc/snippets/SnippetChecker.scala | 26 ++++++++++-- .../scaladoc/snippets/SnippetWrapper.scala | 16 ++++++-- .../scaladoc/tasty/comments/Comments.scala | 41 +++++++++++++++++-- 7 files changed, 88 insertions(+), 22 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index 17e1207324ea..ca55d320d15a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -233,3 +233,5 @@ extension (s: Signature) }.mkString case class TastyMemberSource(val path: java.nio.file.Path, val lineNumber: Int) + +case class SnippetCompilerData(val packageName: String, val classType: Option[String], val classGenerics: Option[String], val imports: List[String]) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala index fe0f6dc45d08..03bf4fb27a46 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala @@ -8,19 +8,17 @@ import dotty.tools.scaladoc.tasty.comments.wiki.WikiDocElement import dotty.tools.scaladoc.tasty.comments.markdown.DocFlexmarkRenderer import dotty.tools.scaladoc.snippets._ -class DocRender(signatureRenderer: SignatureRenderer)(using DocContext): - - private val snippetChecker = SnippetChecker() +class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using DocContext): private val snippetCheckingFunc: Member => String => Unit = (m: Member) => { (str: String) => { - snippetChecker.checkSnippet(str) match { - case r @ SnippetCompilationResult(None, _) => - println(s"In member ${m.name}:") - println(r.getSummary) - case _ => - } + snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData)) match { + case r @ SnippetCompilationResult(None, _) => + println(s"In member ${m.name}:") + println(r.getSummary) + case _ => + } } } diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala index 8d79133a5e39..675d825e4cb8 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala @@ -14,6 +14,8 @@ import java.nio.file.Files import java.nio.file.FileVisitOption import java.io.File +import dotty.tools.scaladoc.snippets._ + case class Page(link: Link, content: Member | ResolvedTemplate | String, children: Seq[Page]): def withNewChildren(newChildren: Seq[Page]) = copy(children = children ++ newChildren) @@ -26,6 +28,7 @@ case class Page(link: Link, content: Member | ResolvedTemplate | String, childre class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx: DocContext) extends SiteRenderer, Resources, Locations, Writer: private val args = summon[DocContext].args + private val snippetChecker = SnippetChecker() val staticSite = summon[DocContext].staticSiteContext val effectiveMembers = members @@ -75,7 +78,7 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx def link(dri: DRI): Option[String] = Some(pathToPage(currentDri, dri)).filter(_ != UnresolvedLocationLink) - MemberRenderer(signatureRenderer).fullMember(m) + MemberRenderer(signatureRenderer, snippetChecker).fullMember(m) case t: ResolvedTemplate => siteContent(page.link.dri, t) case a: String => raw(a) @@ -122,6 +125,7 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx def render(): Unit = val renderedResources = renderResources() val sites = allPages.map(renderPage(_, Vector.empty)) + println(snippetChecker.summary) def mkHead(page: Page): AppliedTag = val resources = page.content match diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala index 0677d148434f..bf32ffa9780d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala @@ -9,8 +9,9 @@ import dotty.tools.scaladoc.tasty.comments.markdown.DocFlexmarkRenderer import com.vladsch.flexmark.util.ast.{Node => MdNode} import dotty.tools.scaladoc.tasty.comments.wiki.WikiDocElement import translators._ +import dotty.tools.scaladoc.snippets._ -class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) extends DocRender(signatureRenderer): +class MemberRenderer(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using DocContext) extends DocRender(signatureRenderer, snippetChecker): import signatureRenderer._ def doc(m: Member): Seq[AppliedTag] = m.docs.fold(Nil)(d => Seq(renderDocPart(d.body)(using m))) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index eadc3f0205ab..8dcf35364156 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -7,7 +7,25 @@ class SnippetChecker( private val compiler: SnippetCompiler = SnippetCompiler(), private val wrapper: SnippetWrapper = SnippetWrapper() ): - def checkSnippet(snippet: String): SnippetCompilationResult = { - val wrapped = wrapper.wrap(snippet) - compiler.compile(wrapped) - } \ No newline at end of file + var warningsCount = 0 + var errorsCount = 0 + + def checkSnippet(snippet: String, data: Option[SnippetCompilerData]): SnippetCompilationResult = { + val wrapped = wrapper.wrap( + snippet, + data.map(_.packageName), + data.flatMap(_.classType), + data.flatMap(_.classGenerics), + data.map(_.imports).getOrElse(Nil) + ) + val res = compiler.compile(wrapped) + if !res.messages.filter(_.level == MessageLevel.Error).isEmpty then errorsCount = errorsCount + 1 + if !res.messages.filter(_.level == MessageLevel.Warning).isEmpty then warningsCount = warningsCount + 1 + res + } + + def summary: String = s""" + |Snippet compiler summary: + | Found $warningsCount warnings + | Found $errorsCount errors + |""".stripMargin \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala index 51bed0840ccf..1916986ba474 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala @@ -5,7 +5,7 @@ import java.io.ByteArrayOutputStream import java.io.PrintStream class SnippetWrapper: - extension (ps: PrintStream) def printlnWithIndent(indent: Int, str: String) = + extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) = ps.println((" " * indent) + str) def wrap(str: String): String = val baos = new ByteArrayOutputStream() @@ -16,6 +16,16 @@ class SnippetWrapper: ps.println("}") baos.toString + def wrap(str:String, packageName: Option[String], className: Option[String], classGenerics: Option[String], imports: List[String]) = + val baos = new ByteArrayOutputStream() + val ps = new PrintStream(baos) + ps.println(s"package ${packageName.getOrElse("snippets")}") + imports.foreach(i => ps.println(s"import $i")) + ps.println(s"trait Snippet${classGenerics.getOrElse("")} { ${className.fold("")(cn => s"self: $cn =>")}") + str.split('\n').foreach(ps.printlnWithIndent(2, _)) + ps.println("}") + baos.toString + object SnippetWrapper: - val lineOffset = 2 - val columnOffset = 2 \ No newline at end of file + private val lineOffset = 2 + private val columnOffset = 2 \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index d9199864a407..432622aa851f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -40,7 +40,8 @@ case class Comment ( groupNames: SortedMap[String, DocPart], groupPrio: SortedMap[String, Int], /** List of conversions to hide - containing e.g: `scala.Predef.FloatArrayOps` */ - hideImplicitConversions: List[DocPart] + hideImplicitConversions: List[DocPart], + snippetCompilerData: SnippetCompilerData ) case class PreparsedComment( @@ -84,8 +85,7 @@ abstract class MarkupConversion[T](val repr: Repr)(using DocContext) { private given qctx.type = qctx object SymOpsWithLinkCache extends SymOpsWithLinkCache - export SymOpsWithLinkCache.dri - export SymOpsWithLinkCache.driInContextOfInheritingParent + export SymOpsWithLinkCache._ def resolveLink(queryStr: String): DocLink = if SchemeUri.matches(queryStr) then DocLink.ToURL(queryStr) @@ -122,6 +122,38 @@ abstract class MarkupConversion[T](val repr: Repr)(using DocContext) { case _ => None } + private def getSnippetCompilerData(sym: qctx.reflect.Symbol): SnippetCompilerData = + val packageName = sym.packageName + if !sym.isPackageDef then sym.tree match { + case c: qctx.reflect.ClassDef => + import qctx.reflect._ + import dotty.tools.dotc + given dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + val cSym = c.symbol.asInstanceOf[dotc.core.Symbols.ClassSymbol] + + def createTypeConstructor(tpe: dotc.core.Types.Type, topLevel: Boolean = true): String = tpe match { + case t @ dotc.core.Types.TypeBounds(upper, lower) => lower match { + case l: dotc.core.Types.HKTypeLambda => + (if topLevel then "" else "?") + l.paramInfos.map(p => createTypeConstructor(p, false)).mkString("[",", ","]") + case _ => (if topLevel then "" else "_") + } + } + val classType = + val ct = cSym.classInfo.selfType.show.replace(".this.",".") + Some(ct) + val classGenerics = Option.when( + !cSym.typeParams.isEmpty + )( + cSym.typeParams.map(_.typeRef).map(t => + t.show + + createTypeConstructor(t.asInstanceOf[dotc.core.Types.TypeRef].underlying) + ).mkString("[",", ","]") + ) + SnippetCompilerData(packageName, classType, classGenerics, Nil) + case _ => getSnippetCompilerData(sym.maybeOwner) + } else SnippetCompilerData(packageName, None, None, Nil) + + final def parse(preparsed: PreparsedComment): Comment = val body = markupToDokkaCommentBody(stringToMarkup(preparsed.body)) Comment( @@ -144,7 +176,8 @@ abstract class MarkupConversion[T](val repr: Repr)(using DocContext) { groupDesc = filterEmpty(preparsed.groupDesc).view.mapValues(markupToDokka).to(SortedMap), groupNames = filterEmpty(preparsed.groupNames).view.mapValues(markupToDokka).to(SortedMap), groupPrio = preparsed.groupPrio, - hideImplicitConversions = filterEmpty(preparsed.hideImplicitConversions).map(markupToDokka) + hideImplicitConversions = filterEmpty(preparsed.hideImplicitConversions).map(markupToDokka), + snippetCompilerData = getSnippetCompilerData(owner) ) } From 9584098abc875b6b752af7695fafcf094f798ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 19 Mar 2021 16:43:47 +0100 Subject: [PATCH 05/28] Add arguments to control snippet compiler. --- .../src/tests/snippetCompilerTest.scala | 14 ++++ .../src/dotty/tools/scaladoc/DocContext.scala | 3 + .../src/dotty/tools/scaladoc/Scaladoc.scala | 6 +- .../tools/scaladoc/ScaladocSettings.scala | 6 ++ scaladoc/src/dotty/tools/scaladoc/api.scala | 7 +- .../scaladoc/renderers/DocRenderer.scala | 23 +++--- .../scaladoc/snippets/SnippetChecker.scala | 40 ++++++---- .../snippets/SnippetCompilerArgs.scala | 80 +++++++++++++++++++ .../markdown/DocFlexmarkExtension.scala | 25 +++--- 9 files changed, 165 insertions(+), 39 deletions(-) create mode 100644 scaladoc-testcases/src/tests/snippetCompilerTest.scala create mode 100644 scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala diff --git a/scaladoc-testcases/src/tests/snippetCompilerTest.scala b/scaladoc-testcases/src/tests/snippetCompilerTest.scala new file mode 100644 index 000000000000..b2d121f2323c --- /dev/null +++ b/scaladoc-testcases/src/tests/snippetCompilerTest.scala @@ -0,0 +1,14 @@ +package snippetCompiler + +/** + * ```scala sc:compile + * def a = 2 + * a + * ``` + * + * ```scala sc:nocompile + * def a = 3 + * a() + * ``` + */ +class A { } \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala index 9f44069fece1..0fd1cc3fcc4a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala @@ -73,6 +73,9 @@ case class NavigationNode(name: String, dri: DRI, nested: Seq[NavigationNode]) case class DocContext(args: Scaladoc.Args, compilerContext: CompilerContext): lazy val sourceLinks = SourceLinks.load(args.sourceLinks, args.revision)(using compilerContext) + + lazy val snippetCompilerArgs = snippets.SnippetCompilerArgs.load(args.snippetCompilerArgs)(using compilerContext) + lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext( File(path).getAbsoluteFile(), args, diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala index 3b9c8e9b9624..29a9db1ad411 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala @@ -49,6 +49,7 @@ object Scaladoc: includePrivateAPI: Boolean = false, docCanonicalBaseUrl: String = "", documentSyntheticTypes: Boolean = false, + snippetCompilerArgs: List[String] = Nil ) def run(args: Array[String], rootContext: CompilerContext): Reporter = @@ -65,7 +66,7 @@ object Scaladoc: val tastyFiles = parsedArgs.tastyFiles ++ parsedArgs.tastyDirs.flatMap(listTastyFiles) if !ctx.reporter.hasErrors then - val updatedArgs = parsedArgs.copy(tastyDirs = Nil, tastyFiles = tastyFiles) + val updatedArgs = parsedArgs.copy(tastyDirs = parsedArgs.tastyDirs, tastyFiles = tastyFiles) if (parsedArgs.output.exists()) util.IO.delete(parsedArgs.output) @@ -189,7 +190,8 @@ object Scaladoc: groups.get, visibilityPrivate.get, docCanonicalBaseUrl.get, - YdocumentSyntheticTypes.get + YdocumentSyntheticTypes.get, + snippetCompilerArgs.get ) (Some(docArgs), newContext) } diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala index 8320a5d6c2f7..252c20f6428b 100644 --- a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala +++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala @@ -94,3 +94,9 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings: val YdocumentSyntheticTypes: Setting[Boolean] = BooleanSetting("-Ydocument-synthetic-types", "Documents intrinsic types e. g. Any, Nothing. Setting is useful only for stdlib", false) + + val snippetCompilerArgs: Setting[List[String]] = + MultiStringSetting("-snippet-compiler-args", "snippet-compiler-args", snippets.SnippetCompilerArgs.usage) + + def scaladocSpecificSettings: Set[Setting[_]] = + Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompilerArgs) diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index ca55d320d15a..ec0b6d226f84 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -234,4 +234,9 @@ extension (s: Signature) case class TastyMemberSource(val path: java.nio.file.Path, val lineNumber: Int) -case class SnippetCompilerData(val packageName: String, val classType: Option[String], val classGenerics: Option[String], val imports: List[String]) +case class SnippetCompilerData( + val packageName: String, + val classType: Option[String], + val classGenerics: Option[String], + val imports: List[String] +) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala index 03bf4fb27a46..390d8b15efbe 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala @@ -8,16 +8,21 @@ import dotty.tools.scaladoc.tasty.comments.wiki.WikiDocElement import dotty.tools.scaladoc.tasty.comments.markdown.DocFlexmarkRenderer import dotty.tools.scaladoc.snippets._ -class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using DocContext): +class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using ctx: DocContext): - private val snippetCheckingFunc: Member => String => Unit = + private val snippetCheckingFunc: Member => (String, Option[SnippetCompilerArg]) => Unit = (m: Member) => { - (str: String) => { - snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData)) match { - case r @ SnippetCompilationResult(None, _) => - println(s"In member ${m.name}:") - println(r.getSummary) - case _ => + (str: String, argOverride: Option[SnippetCompilerArg]) => { + val arg = argOverride.fold( + ctx.snippetCompilerArgs.get(m).fold(SnippetCompilerArg.default)(p => p) + )(p => p) + + snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData), arg).foreach { _ match { + case r @ SnippetCompilationResult(None, _) => + println(s"In member ${m.name} (${m.dri.location}):") + println(r.getSummary) + case _ => + } } } } @@ -62,7 +67,7 @@ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChe case 6 => h6(content) case Paragraph(text) => p(renderElement(text)) case Code(data: String) => - snippetCheckingFunc(m)(data) + snippetCheckingFunc(m)(data, None) pre(code(raw(data))) // TODO add classes case HorizontalRule => hr diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index 8dcf35364156..97e830b5ce0c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -2,26 +2,36 @@ package dotty.tools.scaladoc package snippets import dotty.tools.scaladoc.DocContext +import java.nio.file.Paths -class SnippetChecker( - private val compiler: SnippetCompiler = SnippetCompiler(), +class SnippetChecker()(using ctx: DocContext): + private val sep = System.getProperty("path.separator") + private val cp = System.getProperty("java.class.path") + sep + + Paths.get(ctx.args.classpath).toAbsolutePath + sep + + ctx.args.tastyDirs.map(_.getAbsolutePath()).mkString(sep) + private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp) private val wrapper: SnippetWrapper = SnippetWrapper() -): var warningsCount = 0 var errorsCount = 0 - def checkSnippet(snippet: String, data: Option[SnippetCompilerData]): SnippetCompilationResult = { - val wrapped = wrapper.wrap( - snippet, - data.map(_.packageName), - data.flatMap(_.classType), - data.flatMap(_.classGenerics), - data.map(_.imports).getOrElse(Nil) - ) - val res = compiler.compile(wrapped) - if !res.messages.filter(_.level == MessageLevel.Error).isEmpty then errorsCount = errorsCount + 1 - if !res.messages.filter(_.level == MessageLevel.Warning).isEmpty then warningsCount = warningsCount + 1 - res + def checkSnippet( + snippet: String, + data: Option[SnippetCompilerData], + arg: SnippetCompilerArg + ): Option[SnippetCompilationResult] = { + if arg.is(SCFlags.Compile) then + val wrapped = wrapper.wrap( + snippet, + data.map(_.packageName), + data.flatMap(_.classType), + data.flatMap(_.classGenerics), + data.map(_.imports).getOrElse(Nil) + ) + val res = compiler.compile(wrapped) + if !res.messages.filter(_.level == MessageLevel.Error).isEmpty then errorsCount = errorsCount + 1 + if !res.messages.filter(_.level == MessageLevel.Warning).isEmpty then warningsCount = warningsCount + 1 + Some(res) + else None } def summary: String = s""" diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala new file mode 100644 index 000000000000..d7495960e721 --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala @@ -0,0 +1,80 @@ +package dotty.tools.scaladoc +package snippets + +case class SnippetCompilerArg(flags: Set[SCFlags]): + def is(flag: SCFlags): Boolean = flags.contains(flag) + +object SnippetCompilerArg: + def default: SnippetCompilerArg = SnippetCompilerArg( + Set(SCFlags.NoCompile) + ) + +enum SCFlags(val flagName: String, val forbiddenFlags: Set[SCFlags]): + case Compile extends SCFlags("compile", Set()) + case NoCompile extends SCFlags("nocompile", Set()) + +case class SnippetCompilerArgs(scArgs: PathBased[SnippetCompilerArg]): + def get(member: Member): Option[SnippetCompilerArg] = member.sources.flatMap(s => scArgs.get(s.path).map(_.elem)) + +object SnippetCompilerArgs: + val usage = + """ + |Snippet compiler arguments provide a way to configure snippet checking. + | + |This setting accept list of arguments in format: + |arg := [path=]flag{&flag} + |where path is a prefix of source paths to members to which argument should be set. + | + |If path is not present, argument will be used as default. + | + |Available flags: + |compile - Enables snippet checking. Cannot be used with nocompile. + |nocompile - Disables snippet checking. Cannot be used with compile. + | + """.stripMargin + + def load(args: List[String])(using CompilerContext): SnippetCompilerArgs = { + PathBased.parse[SnippetCompilerArg](args)(using SnippetCompilerArgParser) match { + case PathBased.ParsingResult(errors, res) => + if errors.nonEmpty then report.warning( + s"""Got following errors during snippet compiler args parsing: + |$errors + | + |${usage} + |""".stripMargin + ) + SnippetCompilerArgs(res) + } + } + +object SnippetCompilerArgParser extends ArgParser[SnippetCompilerArg]: + def parse(s: String): Either[String, SnippetCompilerArg] = { + val flagStrings = s.split("&") + val (parsed, errors) = flagStrings.map(flag => SCFlags.values.find(_.flagName == flag).fold( + Left(s"$flag: No such flag found.") + )( + Right(_) + ) + ).partition(_.isRight) + + val (flags, errors2) = parsed.collect { + case Right(flag) => flag + } match { + case list => list.map(f => + list.find(elem => f.forbiddenFlags.contains(elem)) match { + case Some(forbiddenElem) => Left(s"${f.flagName}: Cannot be used with flag: ${forbiddenElem.flagName}") + case None => Right(f) + } + ).partition(_.isRight) + } + + val checkedFlags = flags.collect { + case Right(flag) => flag + }.toSet + + val allErrors = (errors ++ errors2).collect { + case Left(error) => error + }.toList + + if !allErrors.isEmpty then Left(allErrors.mkString("\n")) else Right(SnippetCompilerArg(checkedFlags)) + } \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala index f1b3e3626684..b2bfa750a0de 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala @@ -45,19 +45,21 @@ object DocFlexmarkParser { } } -case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String) => Unit) +case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String, Option[snippets.SnippetCompilerArg]) => Unit) extends HtmlRenderer.HtmlRendererExtension: def rendererOptions(opt: MutableDataHolder): Unit = () // noop - object CodeHandler extends CustomNodeRenderer[ast.Code]: - override def render(node: ast.Code, c: NodeRendererContext, html: HtmlWriter): Unit = - snippetCheckingFunc(node.getText.toString) - c.delegateRender() - - object CodeBlockHandler extends CustomNodeRenderer[ast.CodeBlock]: - override def render(node: ast.CodeBlock, c: NodeRendererContext, html: HtmlWriter): Unit = - snippetCheckingFunc(node.getContentChars.toString) + object FencedCodeBlockHandler extends CustomNodeRenderer[ast.FencedCodeBlock]: + override def render(node: ast.FencedCodeBlock, c: NodeRendererContext, html: HtmlWriter): Unit = + val info = node.getInfo.toString + val argOverride = + info.split(" ") + .find(_.startsWith("sc:")) + .map(_.stripPrefix("sc:")) + .map(snippets.SnippetCompilerArgParser.parse) + .flatMap(_.toOption) + snippetCheckingFunc(node.getContentChars.toString, argOverride) c.delegateRender() object Handler extends CustomNodeRenderer[DocLinkNode]: @@ -68,8 +70,7 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetC override def getNodeRenderingHandlers: JSet[NodeRenderingHandler[_]] = JSet( new NodeRenderingHandler(classOf[DocLinkNode], Handler), - new NodeRenderingHandler(classOf[ast.Code], CodeHandler), - new NodeRenderingHandler(classOf[ast.CodeBlock], CodeBlockHandler) + new NodeRenderingHandler(classOf[ast.FencedCodeBlock], FencedCodeBlockHandler) ) object Factory extends NodeRendererFactory: @@ -79,6 +80,6 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetC htmlRendererBuilder.nodeRendererFactory(Factory) object DocFlexmarkRenderer: - def render(node: Node)(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String) => Unit) = + def render(node: Node)(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String, Option[snippets.SnippetCompilerArg]) => Unit) = val opts = MarkdownParser.mkMarkdownOptions(Seq(DocFlexmarkRenderer(renderLink, snippetCheckingFunc))) HtmlRenderer.builder(opts).build().render(node) From ecb4b2151961021f3458ca983a3378a3b18d7bc4 Mon Sep 17 00:00:00 2001 From: Andrzej Ratajczak Date: Fri, 19 Mar 2021 12:25:47 +0100 Subject: [PATCH 06/28] Start work on handling hide directives in code snippets --- scaladoc-js/src/Main.scala | 1 + .../code-snippets/CodeSnippets.scala | 17 ++++++++++++++ .../src/tests/methodsAndConstructors.scala | 22 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala diff --git a/scaladoc-js/src/Main.scala b/scaladoc-js/src/Main.scala index 7b32aed9a1ea..c31ad58568fa 100644 --- a/scaladoc-js/src/Main.scala +++ b/scaladoc-js/src/Main.scala @@ -3,4 +3,5 @@ package dotty.tools.scaladoc object Main extends App { Searchbar() SocialLinks() + CodeSnippets() } diff --git a/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala b/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala new file mode 100644 index 000000000000..1a15edca5631 --- /dev/null +++ b/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala @@ -0,0 +1,17 @@ +package dotty.tools.scaladoc + +import org.scalajs.dom._ +import org.scalajs.dom.ext._ + +class CodeSnippets: + val replacePatternsAndResults = Seq( + // ("", ""), // hide block directives + ("""(\/\/.*)(\n|\$)""", ""), // single line comment + // ("", ""), // multi line comment + ) + + document.querySelectorAll("code").foreach { + case e: html.Element => e.innerHTML = replacePatternsAndResults.foldLeft(e.innerHTML) { + case (acc, (pattern, result)) => acc.replaceAll(pattern, """$1""") + } + } diff --git a/scaladoc-testcases/src/tests/methodsAndConstructors.scala b/scaladoc-testcases/src/tests/methodsAndConstructors.scala index b8925c593b4c..db657c1921e9 100644 --- a/scaladoc-testcases/src/tests/methodsAndConstructors.scala +++ b/scaladoc-testcases/src/tests/methodsAndConstructors.scala @@ -1,5 +1,27 @@ package tests.methodsAndConstructors + +/** + * This is my codeblock + * + * ``` + * //{{ + * import xd + * import xd2 + * //}} + * + * + * val x = 1 // This is my valid comment + * + * /* + * Is this my comment? + * */ + * + * val y = 2 + * ``` + * + * The end of my codeblock + */ class A class B extends A class C From 506c53800e63db6c0c62276b09df2e46c07ce6fe Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 19 Mar 2021 18:19:05 +0100 Subject: [PATCH 07/28] Hide irrelevant code snippet fragments in scaladoc --- scaladoc-js/resources/scaladoc-searchbar.css | 7 ++++- .../code-snippets/CodeSnippets.scala | 30 +++++++++++++++--- .../src/tests/methodsAndConstructors.scala | 22 ------------- .../src/tests/snippetComments.scala | 31 +++++++++++++++++++ .../tasty/comments/MemberLookup.scala | 5 +-- 5 files changed, 63 insertions(+), 32 deletions(-) create mode 100644 scaladoc-testcases/src/tests/snippetComments.scala diff --git a/scaladoc-js/resources/scaladoc-searchbar.css b/scaladoc-js/resources/scaladoc-searchbar.css index 035adbee6555..a99e02cd805e 100644 --- a/scaladoc-js/resources/scaladoc-searchbar.css +++ b/scaladoc-js/resources/scaladoc-searchbar.css @@ -100,5 +100,10 @@ .pull-right { float: right; - margin-left: auto + margin-left: auto; +} + +.hide-snippet-comments-button { + position: absolute; + left: 90%; } diff --git a/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala b/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala index 1a15edca5631..8beff5ec3a46 100644 --- a/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala +++ b/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala @@ -4,14 +4,34 @@ import org.scalajs.dom._ import org.scalajs.dom.ext._ class CodeSnippets: - val replacePatternsAndResults = Seq( - // ("", ""), // hide block directives - ("""(\/\/.*)(\n|\$)""", ""), // single line comment - // ("", ""), // multi line comment + val replacePatternsAndResults: Seq[(String, String)] = Seq( + """(\/\/{{\n)((.|\n)*?)(\/\/}}\n)""" -> """$2""", // wrap content of block directives + """(\/\/.*?)(\n|\$)""" -> """$1$2""", // wrap single line comment + """(\/\*)((.|\n)*?)(\*\/)""" -> """$0""", // wrap multi line comment ) document.querySelectorAll("code").foreach { case e: html.Element => e.innerHTML = replacePatternsAndResults.foldLeft(e.innerHTML) { - case (acc, (pattern, result)) => acc.replaceAll(pattern, """$1""") + case (acc, (pattern, result)) => + acc.replaceAll(pattern, result) } } + + def toggleHide(e: html.Element | html.Document) = e.querySelectorAll("code span.hideable").foreach { + case e: html.Element if e.style.getPropertyValue("display").isEmpty => e.style.setProperty("display", "none") + case e: html.Element => e.style.removeProperty("display") + } + + toggleHide(document) + + document.querySelectorAll("pre").foreach { + case e: html.Element => + val a = document.createElement("a") + a.textContent = "Show" + a.addEventListener("click", { (_: MouseEvent) => + a.textContent = if a.textContent == "Show" then "Hide" else "Show" + toggleHide(e) + }) + a.classList.add("hide-snippet-comments-button") + e.insertBefore(a, e.firstChild) + } diff --git a/scaladoc-testcases/src/tests/methodsAndConstructors.scala b/scaladoc-testcases/src/tests/methodsAndConstructors.scala index db657c1921e9..b8925c593b4c 100644 --- a/scaladoc-testcases/src/tests/methodsAndConstructors.scala +++ b/scaladoc-testcases/src/tests/methodsAndConstructors.scala @@ -1,27 +1,5 @@ package tests.methodsAndConstructors - -/** - * This is my codeblock - * - * ``` - * //{{ - * import xd - * import xd2 - * //}} - * - * - * val x = 1 // This is my valid comment - * - * /* - * Is this my comment? - * */ - * - * val y = 2 - * ``` - * - * The end of my codeblock - */ class A class B extends A class C diff --git a/scaladoc-testcases/src/tests/snippetComments.scala b/scaladoc-testcases/src/tests/snippetComments.scala new file mode 100644 index 000000000000..c7c229c7d0a2 --- /dev/null +++ b/scaladoc-testcases/src/tests/snippetComments.scala @@ -0,0 +1,31 @@ +package tests.snippetComments + + +/** + * This is my codeblock + * + * ``` + * //{{ + * import xd + * import xd2 + * //}} + * + * + * val x = 1 // This is my valid comment + * + * /* + * multi line comment + * */ + * + * val y = 2 // comment in the same line + * // comment in new line + * val z = 3 + * + * //{{ + * val hideMe = 7 + * //}} + * ``` + * + * The end of my codeblock + */ +class A diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala index 14f14cbedbd1..a6da58ab3e71 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala @@ -151,10 +151,7 @@ trait MemberLookup { } if owner.isPackageDef then - findMatch(hackMembersOf(owner).flatMap { - s => - (if s.name.endsWith("package$") then hackMembersOf(s) else Iterator.empty) ++ Iterator(s) - }) + findMatch(hackMembersOf(owner)) else owner.tree match { case tree: TypeDef => From 82dad667a6761848e48672304f67e7d90e6cfdc4 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Tue, 23 Mar 2021 16:21:08 +0100 Subject: [PATCH 08/28] Apply review suggestions from PR #11822 --- scaladoc-js/resources/scaladoc-searchbar.css | 26 +++++++++++++++-- .../code-snippets/CodeSnippets.scala | 14 ++++++--- .../src/tests/snippetComments.scala | 2 +- .../resources/dotty_res/styles/scalastyle.css | 29 +++++++++++++++---- 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/scaladoc-js/resources/scaladoc-searchbar.css b/scaladoc-js/resources/scaladoc-searchbar.css index a99e02cd805e..7f708b338fe8 100644 --- a/scaladoc-js/resources/scaladoc-searchbar.css +++ b/scaladoc-js/resources/scaladoc-searchbar.css @@ -103,7 +103,29 @@ margin-left: auto; } -.hide-snippet-comments-button { +.snippet-comment-button { position: absolute; - left: 90%; + left: 50%; + width: 24px; + height: 24px; +} + +.show-snippet-comments-button { + -ms-transform: translate(calc(-50% + 5em), -50%); + transform: translate(calc(-50% + 5em), -50%); + background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Ccircle cx='12' cy='12' r='12' fill='white'/%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19 11.0053L19 13.1085H12.9873L12.988 19H10.8714L10.8721 13.1085L5 13.1085L5 11.0053L10.8721 11.0053V5L12.9865 5.00074L12.988 11.0045L19 11.0053Z' fill='%2327282C' fill-opacity='0.75'/%3E %3C/svg%3E"); +} + +.show-snippet-comments-button:hover { + background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Ccircle cx='12' cy='12' r='12' fill='light grey'/%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19 11.0053L19 13.1085H12.9873L12.988 19H10.8714L10.8721 13.1085L5 13.1085L5 11.0053L10.8721 11.0053V5L12.9865 5.00074L12.988 11.0045L19 11.0053Z' fill='%2327282C' fill-opacity='0.75'/%3E %3C/svg%3E"); +} + +.hide-snippet-comments-button { + -ms-transform: translate(calc(-50% + 5em), -50%) rotate(45deg); + transform: translate(calc(-50% + 5em), -50%) rotate(45deg); + background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Ccircle cx='12' cy='12' r='12' fill='white'/%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19 11.0053L19 13.1085H12.9873L12.988 19H10.8714L10.8721 13.1085L5 13.1085L5 11.0053L10.8721 11.0053V5L12.9865 5.00074L12.988 11.0045L19 11.0053Z' fill='%2327282C' fill-opacity='0.75'/%3E %3C/svg%3E"); +} + +.hide-snippet-comments-button:hover { + background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Ccircle cx='12' cy='12' r='12' fill='light grey'/%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19 11.0053L19 13.1085H12.9873L12.988 19H10.8714L10.8721 13.1085L5 13.1085L5 11.0053L10.8721 11.0053V5L12.9865 5.00074L12.988 11.0045L19 11.0053Z' fill='%2327282C' fill-opacity='0.75'/%3E %3C/svg%3E"); } diff --git a/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala b/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala index 8beff5ec3a46..b6ed3d476887 100644 --- a/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala +++ b/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala @@ -25,13 +25,19 @@ class CodeSnippets: toggleHide(document) document.querySelectorAll("pre").foreach { - case e: html.Element => + case e: html.Element if e.querySelectorAll("code span.hideable").nonEmpty => val a = document.createElement("a") - a.textContent = "Show" a.addEventListener("click", { (_: MouseEvent) => - a.textContent = if a.textContent == "Show" then "Hide" else "Show" + if(a.classList.contains("hide-snippet-comments-button")) { + a.classList.add("show-snippet-comments-button") + a.classList.remove("hide-snippet-comments-button") + } else { + a.classList.add("hide-snippet-comments-button") + a.classList.remove("show-snippet-comments-button") + } toggleHide(e) }) - a.classList.add("hide-snippet-comments-button") + a.classList.add("snippet-comment-button") + a.classList.add("show-snippet-comments-button") e.insertBefore(a, e.firstChild) } diff --git a/scaladoc-testcases/src/tests/snippetComments.scala b/scaladoc-testcases/src/tests/snippetComments.scala index c7c229c7d0a2..39b15648103e 100644 --- a/scaladoc-testcases/src/tests/snippetComments.scala +++ b/scaladoc-testcases/src/tests/snippetComments.scala @@ -16,7 +16,7 @@ package tests.snippetComments * /* * multi line comment * */ - * + * val reallyFLongDeclaration = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" * val y = 2 // comment in the same line * // comment in new line * val z = 3 diff --git a/scaladoc/resources/dotty_res/styles/scalastyle.css b/scaladoc/resources/dotty_res/styles/scalastyle.css index dda4e5f08fd1..99a5996781f1 100644 --- a/scaladoc/resources/dotty_res/styles/scalastyle.css +++ b/scaladoc/resources/dotty_res/styles/scalastyle.css @@ -5,7 +5,9 @@ --border-light: #DADFE6; --border-medium: #abc; + --body-bg: #f0f3f6; --code-bg: #F4F5FA; + --documentable-bg: #FFFFFF; --symbol-fg: #333; --link-fg: #00607D; --link-hover-fg: #00A0D0; @@ -45,6 +47,7 @@ body { padding: 0; font-family: "Lato", sans-serif; font-size: 16px; + background-color: var(--body-bg); } /* Page layout */ @@ -75,7 +78,12 @@ h1, h2, h3 { font-family: var(--title-font); color: var(--title-fg); } -pre, code, .monospace, .hljs { +.monospace { + font-family: var(--mono-font); + background: var(--documentable-bg); + font-variant-ligatures: none; +} +pre, code, .hljs { font-family: var(--mono-font); background: var(--code-bg); font-variant-ligatures: none; @@ -84,12 +92,16 @@ code { font-size: .8em; padding: 0 .3em; } +pre { + overflow-x: auto; + scrollbar-width: thin; +} pre code, pre code.hljs { font-size: 1em; padding: 0; } pre, .symbol.monospace { - padding: 10px 8px 10px 12px; + padding: 12px 8px 10px 12px; font-weight: 500; font-size: 12px; } @@ -472,12 +484,18 @@ footer .pull-right { padding-right: 0.5em; min-width: 10em; max-width: 10em; + width: 10em; overflow: hidden; direction: rtl; white-space: nowrap; text-indent: 0em; } +.documentableElement .docs { + width: 100%; + table-layout: fixed; +} + .documentableElement .modifiers .other-modifiers { color: gray; } @@ -518,8 +536,8 @@ footer .pull-right { padding: 5px 4px 5px 4px; font-weight: 500; font-size: 12px; - background: var(--code-bg); - border: 0.25em solid white; + background: var(--documentable-bg); + border: 0.25em solid var(--body-bg); } .documentableElement>div { @@ -528,12 +546,11 @@ footer .pull-right { .expand.documentableElement>div { display: block; - padding-left: 3em; } .expand.documentableElement>div.header { display: block; - padding-left: 7.5em; + padding-left: 4.5em; text-indent: -4.5em; } From b1cc5dac24af2cc967987a5a4757b3337eda55f3 Mon Sep 17 00:00:00 2001 From: Andrzej Ratajczak Date: Tue, 23 Mar 2021 15:44:03 +0100 Subject: [PATCH 09/28] Add returning real line of error in source file of snippet for snippet scaladoc compiler --- .../src/tests/snippetCompilerTest.scala | 3 +- scaladoc/src/dotty/tools/scaladoc/api.scala | 14 +++++--- .../scaladoc/renderers/DocRenderer.scala | 32 +++++++++---------- .../scaladoc/snippets/SnippetChecker.scala | 17 +++++++--- .../scaladoc/snippets/SnippetCompiler.scala | 16 +++------- .../snippets/SnippetCompilerArgs.scala | 2 +- ...ppetWrapper.scala => WrappedSnippet.scala} | 32 +++++++++++++------ .../scaladoc/tasty/comments/Comments.scala | 27 ++++++++++++++-- .../markdown/DocFlexmarkExtension.scala | 8 +++-- 9 files changed, 96 insertions(+), 55 deletions(-) rename scaladoc/src/dotty/tools/scaladoc/snippets/{SnippetWrapper.scala => WrappedSnippet.scala} (62%) diff --git a/scaladoc-testcases/src/tests/snippetCompilerTest.scala b/scaladoc-testcases/src/tests/snippetCompilerTest.scala index b2d121f2323c..fc56a6fbb901 100644 --- a/scaladoc-testcases/src/tests/snippetCompilerTest.scala +++ b/scaladoc-testcases/src/tests/snippetCompilerTest.scala @@ -3,6 +3,7 @@ package snippetCompiler /** * ```scala sc:compile * def a = 2 + * val x = 1 + List() * a * ``` * @@ -11,4 +12,4 @@ package snippetCompiler * a() * ``` */ -class A { } \ No newline at end of file +class A { } diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index ec0b6d226f84..768a6f896fcc 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -232,11 +232,15 @@ extension (s: Signature) case l: Link => l.name }.mkString -case class TastyMemberSource(val path: java.nio.file.Path, val lineNumber: Int) +case class TastyMemberSource(path: java.nio.file.Path, lineNumber: Int) + +object SnippetCompilerData: + case class Position(line: Int, column: Int) case class SnippetCompilerData( - val packageName: String, - val classType: Option[String], - val classGenerics: Option[String], - val imports: List[String] + packageName: String, + classType: Option[String], + classGenerics: Option[String], + imports: List[String], + position: SnippetCompilerData.Position ) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala index 390d8b15efbe..85649e606471 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala @@ -10,22 +10,22 @@ import dotty.tools.scaladoc.snippets._ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using ctx: DocContext): - private val snippetCheckingFunc: Member => (String, Option[SnippetCompilerArg]) => Unit = - (m: Member) => { - (str: String, argOverride: Option[SnippetCompilerArg]) => { - val arg = argOverride.fold( - ctx.snippetCompilerArgs.get(m).fold(SnippetCompilerArg.default)(p => p) - )(p => p) - - snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData), arg).foreach { _ match { - case r @ SnippetCompilationResult(None, _) => - println(s"In member ${m.name} (${m.dri.location}):") - println(r.getSummary) - case _ => + private val snippetCheckingFuncFromMember: Member => SnippetChecker.SnippetCheckingFunc = + (m: Member) => { + (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SnippetCompilerArg]) => { + val arg = argOverride.getOrElse( + ctx.snippetCompilerArgs.get(m).getOrElse(SnippetCompilerArg.default) + ) + + snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData), arg, lineOffset).foreach { _ match { + case r @ SnippetCompilationResult(None, _) => + println(s"In member ${m.name} (${m.dri.location}):") + println(r.getSummary) + case _ => + } } - } + } } - } def renderDocPart(doc: DocPart)(using Member): AppliedTag = doc match case md: MdNode => renderMarkdown(md) @@ -37,7 +37,7 @@ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChe raw(DocFlexmarkRenderer.render(el)( (link,name) => renderLink(link, default => text(if name.isEmpty then default else name)).toString, - snippetCheckingFunc(m) + snippetCheckingFuncFromMember(m) )) private def listItems(items: Seq[WikiDocElement])(using m: Member) = @@ -67,7 +67,7 @@ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChe case 6 => h6(content) case Paragraph(text) => p(renderElement(text)) case Code(data: String) => - snippetCheckingFunc(m)(data, None) + snippetCheckingFuncFromMember(m)(data, 0, None) pre(code(raw(data))) // TODO add classes case HorizontalRule => hr diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index 97e830b5ce0c..3adfe0fb875c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -10,22 +10,25 @@ class SnippetChecker()(using ctx: DocContext): Paths.get(ctx.args.classpath).toAbsolutePath + sep + ctx.args.tastyDirs.map(_.getAbsolutePath()).mkString(sep) private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp) - private val wrapper: SnippetWrapper = SnippetWrapper() + var warningsCount = 0 var errorsCount = 0 def checkSnippet( snippet: String, data: Option[SnippetCompilerData], - arg: SnippetCompilerArg + arg: SnippetCompilerArg, + lineOffset: SnippetChecker.LineOffset ): Option[SnippetCompilationResult] = { if arg.is(SCFlags.Compile) then - val wrapped = wrapper.wrap( + val wrapped = WrappedSnippet( snippet, data.map(_.packageName), data.flatMap(_.classType), data.flatMap(_.classGenerics), - data.map(_.imports).getOrElse(Nil) + data.map(_.imports).getOrElse(Nil), + lineOffset + data.fold(0)(_.position.line) + 1, + data.fold(0)(_.position.column) ) val res = compiler.compile(wrapped) if !res.messages.filter(_.level == MessageLevel.Error).isEmpty then errorsCount = errorsCount + 1 @@ -38,4 +41,8 @@ class SnippetChecker()(using ctx: DocContext): |Snippet compiler summary: | Found $warningsCount warnings | Found $errorsCount errors - |""".stripMargin \ No newline at end of file + |""".stripMargin + +object SnippetChecker: + type LineOffset = Int + type SnippetCheckingFunc = (String, LineOffset, Option[SnippetCompilerArg]) => Unit diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index 1dad4af1d88a..0c92286ca3c7 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -42,14 +42,14 @@ class SnippetCompiler( private def nullableMessage(msgOrNull: String): String = if (msgOrNull == null) "" else msgOrNull - private def createReportMessage(diagnostics: Seq[Diagnostic]): Seq[SnippetCompilerMessage] = { + private def createReportMessage(diagnostics: Seq[Diagnostic], line: Int, column: Int): Seq[SnippetCompilerMessage] = { val infos = diagnostics.toSeq.sortBy(_.pos.source.path) val errorMessages = infos.map { case diagnostic if diagnostic.position.isPresent => val pos = diagnostic.position.get val msg = nullableMessage(diagnostic.message) val level = MessageLevel.fromOrdinal(diagnostic.level) - SnippetCompilerMessage(pos.line, pos.column, pos.lineContent, msg, level) + SnippetCompilerMessage(pos.line + line, pos.column + column, pos.lineContent, msg, level) case d => val level = MessageLevel.fromOrdinal(d.level) SnippetCompilerMessage(-1, -1, "", nullableMessage(d.message), level) @@ -58,7 +58,7 @@ class SnippetCompiler( } def compile( - snippets: List[String] + wrappedSnippet: WrappedSnippet ): SnippetCompilationResult = { val context = driver.currentCtx.fresh .setSetting( @@ -67,14 +67,8 @@ class SnippetCompiler( ) .setReporter(new StoreReporter) val run = newRun(using context) - run.compileFromStrings(snippets) - val messages = createReportMessage(context.reporter.pendingMessages(using context)) + run.compileFromStrings(List(wrappedSnippet.snippet)) + val messages = createReportMessage(context.reporter.pendingMessages(using context), wrappedSnippet.lineOffset, wrappedSnippet.columnOffset) val targetIfSuccessful = Option.when(!context.reporter.hasErrors)(target) SnippetCompilationResult(targetIfSuccessful, messages) } - - def compile( - snippet: String - ): SnippetCompilationResult = compile(List(snippet)) - - diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala index d7495960e721..f880169d78e0 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala @@ -77,4 +77,4 @@ object SnippetCompilerArgParser extends ArgParser[SnippetCompilerArg]: }.toList if !allErrors.isEmpty then Left(allErrors.mkString("\n")) else Right(SnippetCompilerArg(checkedFlags)) - } \ No newline at end of file + } diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala similarity index 62% rename from scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala rename to scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala index 1916986ba474..91229cb148a3 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetWrapper.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala @@ -4,19 +4,30 @@ package snippets import java.io.ByteArrayOutputStream import java.io.PrintStream -class SnippetWrapper: - extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) = - ps.println((" " * indent) + str) - def wrap(str: String): String = +case class WrappedSnippet(snippet: String, lineOffset: Int, columnOffset: Int) + +object WrappedSnippet: + private val lineOffset = 2 + private val columnOffset = 2 + + def apply(str: String): WrappedSnippet = val baos = new ByteArrayOutputStream() val ps = new PrintStream(baos) ps.println("package snippets") ps.println("object Snippet {") str.split('\n').foreach(ps.printlnWithIndent(2, _)) ps.println("}") - baos.toString + WrappedSnippet(baos.toString, lineOffset, columnOffset) - def wrap(str:String, packageName: Option[String], className: Option[String], classGenerics: Option[String], imports: List[String]) = + def apply( + str: String, + packageName: Option[String], + className: Option[String], + classGenerics: Option[String], + imports: List[String], + lineOffset: Int, + columnOffset: Int + ): WrappedSnippet = val baos = new ByteArrayOutputStream() val ps = new PrintStream(baos) ps.println(s"package ${packageName.getOrElse("snippets")}") @@ -24,8 +35,9 @@ class SnippetWrapper: ps.println(s"trait Snippet${classGenerics.getOrElse("")} { ${className.fold("")(cn => s"self: $cn =>")}") str.split('\n').foreach(ps.printlnWithIndent(2, _)) ps.println("}") - baos.toString + WrappedSnippet(baos.toString, lineOffset, columnOffset) + + extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) = + ps.println((" " * indent) + str) + -object SnippetWrapper: - private val lineOffset = 2 - private val columnOffset = 2 \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index 432622aa851f..977e48689647 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -149,10 +149,31 @@ abstract class MarkupConversion[T](val repr: Repr)(using DocContext) { createTypeConstructor(t.asInstanceOf[dotc.core.Types.TypeRef].underlying) ).mkString("[",", ","]") ) - SnippetCompilerData(packageName, classType, classGenerics, Nil) + SnippetCompilerData(packageName, classType, classGenerics, Nil, position(hackGetPositionOfDocstring(using qctx)(sym))) case _ => getSnippetCompilerData(sym.maybeOwner) - } else SnippetCompilerData(packageName, None, None, Nil) - + } else SnippetCompilerData(packageName, None, None, Nil, position(hackGetPositionOfDocstring(using qctx)(sym))) + + private def position(p: Option[qctx.reflect.Position]): SnippetCompilerData.Position = + p.fold(SnippetCompilerData.Position(0, 0))(p => SnippetCompilerData.Position(p.startLine, p.startColumn)) + + private def hackGetPositionOfDocstring(using Quotes)(s: qctx.reflect.Symbol): Option[qctx.reflect.Position] = + import dotty.tools.dotc.core.Comments.CommentsContext + import dotty.tools.dotc + given ctx: dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + val docCtx = ctx.docCtx.getOrElse { + throw new RuntimeException( + "DocCtx could not be found and documentations are unavailable. This is a compiler-internal error." + ) + } + val span = docCtx.docstring(s.asInstanceOf[dotc.core.Symbols.Symbol]).span + s.pos.flatMap { pos => + docCtx.docstring(s.asInstanceOf[dotc.core.Symbols.Symbol]).map { docstring => + dotty.tools.dotc.util.SourcePosition( + pos.sourceFile.asInstanceOf[dotty.tools.dotc.util.SourceFile], + docstring.span + ).asInstanceOf[qctx.reflect.Position] + } + } final def parse(preparsed: PreparsedComment): Comment = val body = markupToDokkaCommentBody(stringToMarkup(preparsed.body)) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala index b2bfa750a0de..31a263555a88 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala @@ -12,6 +12,8 @@ import com.vladsch.flexmark.util.options._ import com.vladsch.flexmark.util.sequence.BasedSequence import com.vladsch.flexmark._ +import dotty.tools.scaladoc.snippets.SnippetChecker + class DocLinkNode( val target: DocLink, val body: String, @@ -45,7 +47,7 @@ object DocFlexmarkParser { } } -case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String, Option[snippets.SnippetCompilerArg]) => Unit) +case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetCheckingFunc: SnippetChecker.SnippetCheckingFunc) extends HtmlRenderer.HtmlRendererExtension: def rendererOptions(opt: MutableDataHolder): Unit = () // noop @@ -59,7 +61,7 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetC .map(_.stripPrefix("sc:")) .map(snippets.SnippetCompilerArgParser.parse) .flatMap(_.toOption) - snippetCheckingFunc(node.getContentChars.toString, argOverride) + snippetCheckingFunc(node.getContentChars.toString, node.getStartLineNumber, argOverride) c.delegateRender() object Handler extends CustomNodeRenderer[DocLinkNode]: @@ -80,6 +82,6 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetC htmlRendererBuilder.nodeRendererFactory(Factory) object DocFlexmarkRenderer: - def render(node: Node)(renderLink: (DocLink, String) => String, snippetCheckingFunc: (String, Option[snippets.SnippetCompilerArg]) => Unit) = + def render(node: Node)(renderLink: (DocLink, String) => String, snippetCheckingFunc: SnippetChecker.SnippetCheckingFunc) = val opts = MarkdownParser.mkMarkdownOptions(Seq(DocFlexmarkRenderer(renderLink, snippetCheckingFunc))) HtmlRenderer.builder(opts).build().render(node) From 56e8b0c1681c55b9eddda48d54b64f0415bb3d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 26 Mar 2021 13:42:03 +0100 Subject: [PATCH 10/28] Add failing flag --- .../src/tests/snippetCompilerTest.scala | 6 +++- .../scaladoc/renderers/DocRenderer.scala | 4 +-- .../scaladoc/snippets/SnippetChecker.scala | 10 +++--- .../snippets/SnippetCompilationResult.scala | 19 +++++++++-- .../scaladoc/snippets/SnippetCompiler.scala | 33 +++++++++++++++---- .../snippets/SnippetCompilerArgs.scala | 1 + 6 files changed, 55 insertions(+), 18 deletions(-) diff --git a/scaladoc-testcases/src/tests/snippetCompilerTest.scala b/scaladoc-testcases/src/tests/snippetCompilerTest.scala index fc56a6fbb901..87a9095e2fee 100644 --- a/scaladoc-testcases/src/tests/snippetCompilerTest.scala +++ b/scaladoc-testcases/src/tests/snippetCompilerTest.scala @@ -1,12 +1,16 @@ package snippetCompiler /** - * ```scala sc:compile + * ```scala sc:compile&failing * def a = 2 * val x = 1 + List() * a * ``` * + * ```scala sc:compile&failing + * def a = 2 + * ``` + * * ```scala sc:nocompile * def a = 3 * a() diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala index 85649e606471..184c2f8d13d1 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala @@ -16,9 +16,9 @@ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChe val arg = argOverride.getOrElse( ctx.snippetCompilerArgs.get(m).getOrElse(SnippetCompilerArg.default) ) - + snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData), arg, lineOffset).foreach { _ match { - case r @ SnippetCompilationResult(None, _) => + case r: SnippetCompilationResult if !r.isSuccessful => println(s"In member ${m.name} (${m.dri.location}):") println(r.getSummary) case _ => diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index 3adfe0fb875c..80d5333b75cf 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -11,8 +11,8 @@ class SnippetChecker()(using ctx: DocContext): ctx.args.tastyDirs.map(_.getAbsolutePath()).mkString(sep) private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp) - var warningsCount = 0 - var errorsCount = 0 + private var warningsCount = 0 + private var errorsCount = 0 def checkSnippet( snippet: String, @@ -30,9 +30,9 @@ class SnippetChecker()(using ctx: DocContext): lineOffset + data.fold(0)(_.position.line) + 1, data.fold(0)(_.position.column) ) - val res = compiler.compile(wrapped) - if !res.messages.filter(_.level == MessageLevel.Error).isEmpty then errorsCount = errorsCount + 1 - if !res.messages.filter(_.level == MessageLevel.Warning).isEmpty then warningsCount = warningsCount + 1 + val res = compiler.compile(wrapped, arg) + if !res.isSuccessful && res.messages.exists(_.level == MessageLevel.Error) then errorsCount = errorsCount + 1 + if !res.isSuccessful && res.messages.exists(_.level == MessageLevel.Warning) then warningsCount = warningsCount + 1 Some(res) else None } diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala index 2083b880fe07..708bcac8a0f9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala @@ -3,10 +3,23 @@ package snippets import dotty.tools.io.{ AbstractFile } -case class SnippetCompilerMessage(line: Int, column: Int, sourceLine: String, message: String, level: MessageLevel) +case class Position(line: Int, column: Int, sourceLine: String) -case class SnippetCompilationResult(result: Option[AbstractFile], messages: Seq[SnippetCompilerMessage]): - def getSummary: String = messages.map(m => s"At ${m.line}:${m.column}:\n${m.sourceLine}${m.level.text}: ${m.message}").mkString("\n") +case class SnippetCompilerMessage(position: Option[Position], message: String, level: MessageLevel) + +case class SnippetCompilationResult( + isSuccessful: Boolean, + result: Option[AbstractFile], + messages: Seq[SnippetCompilerMessage] +): + def getSummary: String = + messages.map(m => + m.position.fold( + s"${m.level.text}: ${m.message}" + )(pos => + s"At ${pos.line}:${pos.column}:\n${pos.sourceLine}${m.level.text}: ${m.message}" + ) + ).mkString("\n") enum MessageLevel(val text: String): diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index 0c92286ca3c7..bb7bcfaf0fec 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -46,19 +46,34 @@ class SnippetCompiler( val infos = diagnostics.toSeq.sortBy(_.pos.source.path) val errorMessages = infos.map { case diagnostic if diagnostic.position.isPresent => - val pos = diagnostic.position.get + val diagPos = diagnostic.position.get + val pos = Some( + Position(diagPos.line + line, diagPos.column + column, diagPos.lineContent) + ) val msg = nullableMessage(diagnostic.message) val level = MessageLevel.fromOrdinal(diagnostic.level) - SnippetCompilerMessage(pos.line + line, pos.column + column, pos.lineContent, msg, level) + SnippetCompilerMessage(pos, msg, level) case d => val level = MessageLevel.fromOrdinal(d.level) - SnippetCompilerMessage(-1, -1, "", nullableMessage(d.message), level) + SnippetCompilerMessage(None, nullableMessage(d.message), level) } errorMessages } + private def additionalMessages(arg: SnippetCompilerArg, context: Context): Seq[SnippetCompilerMessage] = { + Option.when(arg.is(SCFlags.Fail) && !context.reporter.hasErrors)( + SnippetCompilerMessage(None, "Snippet should not compile but compiled succesfully", MessageLevel.Error) + ).toList + } + + private def isSuccessful(arg: SnippetCompilerArg, context: Context): Boolean = { + if arg.is(SCFlags.Fail) then context.reporter.hasErrors + else !context.reporter.hasErrors + } + def compile( - wrappedSnippet: WrappedSnippet + wrappedSnippet: WrappedSnippet, + arg: SnippetCompilerArg ): SnippetCompilationResult = { val context = driver.currentCtx.fresh .setSetting( @@ -68,7 +83,11 @@ class SnippetCompiler( .setReporter(new StoreReporter) val run = newRun(using context) run.compileFromStrings(List(wrappedSnippet.snippet)) - val messages = createReportMessage(context.reporter.pendingMessages(using context), wrappedSnippet.lineOffset, wrappedSnippet.columnOffset) - val targetIfSuccessful = Option.when(!context.reporter.hasErrors)(target) - SnippetCompilationResult(targetIfSuccessful, messages) + + val messages = + createReportMessage(context.reporter.pendingMessages(using context), wrappedSnippet.lineOffset, wrappedSnippet.columnOffset) ++ + additionalMessages(arg, context) + + val t = Option.when(!context.reporter.hasErrors)(target) + SnippetCompilationResult(isSuccessful(arg, context), t, messages) } diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala index f880169d78e0..e27db4afc007 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala @@ -12,6 +12,7 @@ object SnippetCompilerArg: enum SCFlags(val flagName: String, val forbiddenFlags: Set[SCFlags]): case Compile extends SCFlags("compile", Set()) case NoCompile extends SCFlags("nocompile", Set()) + case Fail extends SCFlags("failing", Set()) case class SnippetCompilerArgs(scArgs: PathBased[SnippetCompilerArg]): def get(member: Member): Option[SnippetCompilerArg] = member.sources.flatMap(s => scArgs.get(s.path).map(_.elem)) From b3b7d0bf6c9519f6c5cfc0cf63e2aa73031bbe99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 26 Mar 2021 16:17:26 +0100 Subject: [PATCH 11/28] Add debug flag. --- .../src/tests/snippetCompilerTest.scala | 6 ++++ .../snippets/SnippetCompilationResult.scala | 1 + .../scaladoc/snippets/SnippetCompiler.scala | 13 ++++++--- .../snippets/SnippetCompilerArgs.scala | 29 ++++++++++++------- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/scaladoc-testcases/src/tests/snippetCompilerTest.scala b/scaladoc-testcases/src/tests/snippetCompilerTest.scala index 87a9095e2fee..7115366ff59c 100644 --- a/scaladoc-testcases/src/tests/snippetCompilerTest.scala +++ b/scaladoc-testcases/src/tests/snippetCompilerTest.scala @@ -7,6 +7,12 @@ package snippetCompiler * a * ``` * + * ```scala sc:compile&debug + * def a = 2 + * val x = 1 + List() + * a + * ``` + * * ```scala sc:compile&failing * def a = 2 * ``` diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala index 708bcac8a0f9..d4cdd1834357 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala @@ -26,4 +26,5 @@ enum MessageLevel(val text: String): case Info extends MessageLevel("Info") case Warning extends MessageLevel("Warning") case Error extends MessageLevel("Error") + case Debug extends MessageLevel("Debug") diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index bb7bcfaf0fec..69f901a43543 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -60,9 +60,14 @@ class SnippetCompiler( errorMessages } - private def additionalMessages(arg: SnippetCompilerArg, context: Context): Seq[SnippetCompilerMessage] = { - Option.when(arg.is(SCFlags.Fail) && !context.reporter.hasErrors)( - SnippetCompilerMessage(None, "Snippet should not compile but compiled succesfully", MessageLevel.Error) + private def additionalMessages(wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg, context: Context): Seq[SnippetCompilerMessage] = { + ( + Option.when(arg.is(SCFlags.Fail) && !context.reporter.hasErrors)( + SnippetCompilerMessage(None, "Snippet should not compile but compiled succesfully", MessageLevel.Error) + ) ++ + Option.when(arg.is(SCFlags.Debug))( + SnippetCompilerMessage(None, s"\n${wrappedSnippet.snippet}", MessageLevel.Debug) + ) ).toList } @@ -86,7 +91,7 @@ class SnippetCompiler( val messages = createReportMessage(context.reporter.pendingMessages(using context), wrappedSnippet.lineOffset, wrappedSnippet.columnOffset) ++ - additionalMessages(arg, context) + additionalMessages(wrappedSnippet, arg, context) val t = Option.when(!context.reporter.hasErrors)(target) SnippetCompilationResult(isSuccessful(arg, context), t, messages) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala index e27db4afc007..4caa8f8611fb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala @@ -9,10 +9,15 @@ object SnippetCompilerArg: Set(SCFlags.NoCompile) ) -enum SCFlags(val flagName: String, val forbiddenFlags: Set[SCFlags]): - case Compile extends SCFlags("compile", Set()) - case NoCompile extends SCFlags("nocompile", Set()) - case Fail extends SCFlags("failing", Set()) +sealed trait SCFlags(val flagName: String, val forbiddenFlags: Set[SCFlags]) + +object SCFlags: + case object Compile extends SCFlags("compile", Set(NoCompile)) + case object NoCompile extends SCFlags("nocompile", Set(Compile)) + case object Fail extends SCFlags("failing", Set()) + case object Debug extends SCFlags("debug", Set()) + + def values: Seq[SCFlags] = Seq(Compile, NoCompile, Fail, Debug) case class SnippetCompilerArgs(scArgs: PathBased[SnippetCompilerArg]): def get(member: Member): Option[SnippetCompilerArg] = member.sources.flatMap(s => scArgs.get(s.path).map(_.elem)) @@ -58,15 +63,19 @@ object SnippetCompilerArgParser extends ArgParser[SnippetCompilerArg]: ) ).partition(_.isRight) + val (flags, errors2) = parsed.collect { case Right(flag) => flag } match { - case list => list.map(f => - list.find(elem => f.forbiddenFlags.contains(elem)) match { - case Some(forbiddenElem) => Left(s"${f.flagName}: Cannot be used with flag: ${forbiddenElem.flagName}") - case None => Right(f) - } - ).partition(_.isRight) + case list => + list.map(f => + list.find(elem => + f.forbiddenFlags.contains(elem) + ) match { + case Some(forbiddenElem) => Left(s"${f.flagName}: Cannot be used with flag: ${forbiddenElem.flagName}") + case None => Right(f) + } + ).partition(_.isRight) } val checkedFlags = flags.collect { From 78676bc5444136fec5b23ce30974e394ac0070a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 26 Mar 2021 16:29:02 +0100 Subject: [PATCH 12/28] Add addition operation for snippet compiler arg --- .../dotty/tools/scaladoc/renderers/DocRenderer.scala | 11 +++++++++-- .../tools/scaladoc/snippets/SnippetCompilerArgs.scala | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala index 184c2f8d13d1..fbad8f4bb1fa 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala @@ -13,8 +13,15 @@ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChe private val snippetCheckingFuncFromMember: Member => SnippetChecker.SnippetCheckingFunc = (m: Member) => { (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SnippetCompilerArg]) => { - val arg = argOverride.getOrElse( - ctx.snippetCompilerArgs.get(m).getOrElse(SnippetCompilerArg.default) + val pathBasedArg = ctx.snippetCompilerArgs.get(m).fold( + SnippetCompilerArg.default + )( + _ + SnippetCompilerArg.default + ) + val arg = argOverride.fold( + pathBasedArg + )( + _ + pathBasedArg ) snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData), arg, lineOffset).foreach { _ match { diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala index 4caa8f8611fb..af931eded11d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala @@ -3,6 +3,11 @@ package snippets case class SnippetCompilerArg(flags: Set[SCFlags]): def is(flag: SCFlags): Boolean = flags.contains(flag) + def +(other: SnippetCompilerArg): SnippetCompilerArg = { + val allNoncompatbileFlags = flags.flatMap(_.forbiddenFlags) + val compatibleFlags = other.flags.filter(flag => !allNoncompatbileFlags.contains(flag)) + copy(flags = flags ++ compatibleFlags) + } object SnippetCompilerArg: def default: SnippetCompilerArg = SnippetCompilerArg( From 2fe91f343179996ad226e5c9f0f5e458352126cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 29 Mar 2021 10:46:00 +0200 Subject: [PATCH 13/28] Refactor snippet compiler args --- .../src/tests/snippetCompilerTest.scala | 6 +- .../src/dotty/tools/scaladoc/DocContext.scala | 2 +- .../src/dotty/tools/scaladoc/Scaladoc.scala | 6 +- .../tools/scaladoc/ScaladocSettings.scala | 9 +- .../scaladoc/renderers/DocRenderer.scala | 16 +--- .../scaladoc/snippets/SnippetChecker.scala | 4 +- .../scaladoc/snippets/SnippetCompiler.scala | 6 +- .../snippets/SnippetCompilerArgs.scala | 95 +++++++------------ .../markdown/DocFlexmarkExtension.scala | 2 +- 9 files changed, 56 insertions(+), 90 deletions(-) diff --git a/scaladoc-testcases/src/tests/snippetCompilerTest.scala b/scaladoc-testcases/src/tests/snippetCompilerTest.scala index 7115366ff59c..0cce0dd6e65b 100644 --- a/scaladoc-testcases/src/tests/snippetCompilerTest.scala +++ b/scaladoc-testcases/src/tests/snippetCompilerTest.scala @@ -1,19 +1,19 @@ package snippetCompiler /** - * ```scala sc:compile&failing + * ```scala sc:compile * def a = 2 * val x = 1 + List() * a * ``` * - * ```scala sc:compile&debug + * ```scala sc:failing * def a = 2 * val x = 1 + List() * a * ``` * - * ```scala sc:compile&failing + * ```scala sc:failing * def a = 2 * ``` * diff --git a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala index 0fd1cc3fcc4a..af1f40499142 100644 --- a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala @@ -74,7 +74,7 @@ case class NavigationNode(name: String, dri: DRI, nested: Seq[NavigationNode]) case class DocContext(args: Scaladoc.Args, compilerContext: CompilerContext): lazy val sourceLinks = SourceLinks.load(args.sourceLinks, args.revision)(using compilerContext) - lazy val snippetCompilerArgs = snippets.SnippetCompilerArgs.load(args.snippetCompilerArgs)(using compilerContext) + lazy val snippetCompilerArgs = snippets.SnippetCompilerArgs.load(args.snippetCompiler, args.snippetCompilerDebug)(using compilerContext) lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext( File(path).getAbsoluteFile(), diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala index 29a9db1ad411..f227a33aa561 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala @@ -49,7 +49,8 @@ object Scaladoc: includePrivateAPI: Boolean = false, docCanonicalBaseUrl: String = "", documentSyntheticTypes: Boolean = false, - snippetCompilerArgs: List[String] = Nil + snippetCompiler: List[String] = Nil, + snippetCompilerDebug: Boolean = false ) def run(args: Array[String], rootContext: CompilerContext): Reporter = @@ -191,7 +192,8 @@ object Scaladoc: visibilityPrivate.get, docCanonicalBaseUrl.get, YdocumentSyntheticTypes.get, - snippetCompilerArgs.get + snippetCompiler.get, + snippetCompilerDebug.get ) (Some(docArgs), newContext) } diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala index 252c20f6428b..cbe8d8215214 100644 --- a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala +++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala @@ -95,8 +95,11 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings: val YdocumentSyntheticTypes: Setting[Boolean] = BooleanSetting("-Ydocument-synthetic-types", "Documents intrinsic types e. g. Any, Nothing. Setting is useful only for stdlib", false) - val snippetCompilerArgs: Setting[List[String]] = - MultiStringSetting("-snippet-compiler-args", "snippet-compiler-args", snippets.SnippetCompilerArgs.usage) + val snippetCompiler: Setting[List[String]] = + MultiStringSetting("-snippet-compiler", "snippet-compiler", snippets.SnippetCompilerArgs.usage) + + val snippetCompilerDebug: Setting[Boolean] = + BooleanSetting("-snippet-compiler-debug", snippets.SnippetCompilerArgs.debugUsage, false) def scaladocSpecificSettings: Set[Setting[_]] = - Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompilerArgs) + Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, snippetCompilerDebug) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala index fbad8f4bb1fa..b9f567932da8 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala @@ -10,19 +10,11 @@ import dotty.tools.scaladoc.snippets._ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using ctx: DocContext): - private val snippetCheckingFuncFromMember: Member => SnippetChecker.SnippetCheckingFunc = + private def snippetCheckingFuncFromMember: Member => SnippetChecker.SnippetCheckingFunc = (m: Member) => { - (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SnippetCompilerArg]) => { - val pathBasedArg = ctx.snippetCompilerArgs.get(m).fold( - SnippetCompilerArg.default - )( - _ + SnippetCompilerArg.default - ) - val arg = argOverride.fold( - pathBasedArg - )( - _ + pathBasedArg - ) + (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => { + val pathBasedArg = ctx.snippetCompilerArgs.get(m) + val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_)) snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData), arg, lineOffset).foreach { _ match { case r: SnippetCompilationResult if !r.isSuccessful => diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index 80d5333b75cf..3debdb0a116a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -20,7 +20,7 @@ class SnippetChecker()(using ctx: DocContext): arg: SnippetCompilerArg, lineOffset: SnippetChecker.LineOffset ): Option[SnippetCompilationResult] = { - if arg.is(SCFlags.Compile) then + if arg.flag != SCFlags.NoCompile then val wrapped = WrappedSnippet( snippet, data.map(_.packageName), @@ -45,4 +45,4 @@ class SnippetChecker()(using ctx: DocContext): object SnippetChecker: type LineOffset = Int - type SnippetCheckingFunc = (String, LineOffset, Option[SnippetCompilerArg]) => Unit + type SnippetCheckingFunc = (String, LineOffset, Option[SCFlags]) => Unit diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index 69f901a43543..bedea9f1dda1 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -62,17 +62,17 @@ class SnippetCompiler( private def additionalMessages(wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg, context: Context): Seq[SnippetCompilerMessage] = { ( - Option.when(arg.is(SCFlags.Fail) && !context.reporter.hasErrors)( + Option.when(arg.flag == SCFlags.Fail && !context.reporter.hasErrors)( SnippetCompilerMessage(None, "Snippet should not compile but compiled succesfully", MessageLevel.Error) ) ++ - Option.when(arg.is(SCFlags.Debug))( + Option.when(arg.debug && !isSuccessful(arg, context))( SnippetCompilerMessage(None, s"\n${wrappedSnippet.snippet}", MessageLevel.Debug) ) ).toList } private def isSuccessful(arg: SnippetCompilerArg, context: Context): Boolean = { - if arg.is(SCFlags.Fail) then context.reporter.hasErrors + if arg.flag == SCFlags.Fail then context.reporter.hasErrors else !context.reporter.hasErrors } diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala index af931eded11d..fd03f8683964 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala @@ -1,31 +1,23 @@ package dotty.tools.scaladoc package snippets -case class SnippetCompilerArg(flags: Set[SCFlags]): - def is(flag: SCFlags): Boolean = flags.contains(flag) - def +(other: SnippetCompilerArg): SnippetCompilerArg = { - val allNoncompatbileFlags = flags.flatMap(_.forbiddenFlags) - val compatibleFlags = other.flags.filter(flag => !allNoncompatbileFlags.contains(flag)) - copy(flags = flags ++ compatibleFlags) - } - -object SnippetCompilerArg: - def default: SnippetCompilerArg = SnippetCompilerArg( - Set(SCFlags.NoCompile) - ) +case class SnippetCompilerArg(flag: SCFlags, debug: Boolean): + def overrideFlag(f: SCFlags): SnippetCompilerArg = copy(flag = f) -sealed trait SCFlags(val flagName: String, val forbiddenFlags: Set[SCFlags]) +sealed trait SCFlags(val flagName: String) object SCFlags: - case object Compile extends SCFlags("compile", Set(NoCompile)) - case object NoCompile extends SCFlags("nocompile", Set(Compile)) - case object Fail extends SCFlags("failing", Set()) - case object Debug extends SCFlags("debug", Set()) + case object Compile extends SCFlags("compile") + case object NoCompile extends SCFlags("nocompile") + case object Fail extends SCFlags("failing") - def values: Seq[SCFlags] = Seq(Compile, NoCompile, Fail, Debug) + def values: Seq[SCFlags] = Seq(Compile, NoCompile, Fail) -case class SnippetCompilerArgs(scArgs: PathBased[SnippetCompilerArg]): - def get(member: Member): Option[SnippetCompilerArg] = member.sources.flatMap(s => scArgs.get(s.path).map(_.elem)) +case class SnippetCompilerArgs(scFlags: PathBased[SCFlags], val debug: Boolean, defaultFlag: SCFlags): + def get(member: Member): SnippetCompilerArg = + member.sources + .flatMap(s => scFlags.get(s.path).map(_.elem)) + .fold(SnippetCompilerArg(defaultFlag, debug))(SnippetCompilerArg(_, debug)) object SnippetCompilerArgs: val usage = @@ -33,63 +25,40 @@ object SnippetCompilerArgs: |Snippet compiler arguments provide a way to configure snippet checking. | |This setting accept list of arguments in format: - |arg := [path=]flag{&flag} + |args := arg{,arg} + |arg := [path=]flag |where path is a prefix of source paths to members to which argument should be set. | |If path is not present, argument will be used as default. | |Available flags: - |compile - Enables snippet checking. Cannot be used with nocompile. - |nocompile - Disables snippet checking. Cannot be used with compile. + |compile - Enables snippet checking. + |nocompile - Disables snippet checking. + |failing - Enables snippet checking, asserts that snippet doesn't compile. | """.stripMargin - def load(args: List[String])(using CompilerContext): SnippetCompilerArgs = { - PathBased.parse[SnippetCompilerArg](args)(using SnippetCompilerArgParser) match { + val debugUsage = """ + |Setting this option causes snippet compiler to print snippet as it is compiled (after wrapping). + """.stripMargin + + def load(args: List[String], debug: Boolean, defaultFlag: SCFlags = SCFlags.NoCompile)(using CompilerContext): SnippetCompilerArgs = { + PathBased.parse[SCFlags](args)(using SCFlagsParser) match { case PathBased.ParsingResult(errors, res) => - if errors.nonEmpty then report.warning( - s"""Got following errors during snippet compiler args parsing: + if errors.nonEmpty then report.warning(s""" + |Got following errors during snippet compiler args parsing: |$errors | - |${usage} + |$usage |""".stripMargin ) - SnippetCompilerArgs(res) + SnippetCompilerArgs(res, debug, defaultFlag) } } -object SnippetCompilerArgParser extends ArgParser[SnippetCompilerArg]: - def parse(s: String): Either[String, SnippetCompilerArg] = { - val flagStrings = s.split("&") - val (parsed, errors) = flagStrings.map(flag => SCFlags.values.find(_.flagName == flag).fold( - Left(s"$flag: No such flag found.") - )( - Right(_) - ) - ).partition(_.isRight) - - - val (flags, errors2) = parsed.collect { - case Right(flag) => flag - } match { - case list => - list.map(f => - list.find(elem => - f.forbiddenFlags.contains(elem) - ) match { - case Some(forbiddenElem) => Left(s"${f.flagName}: Cannot be used with flag: ${forbiddenElem.flagName}") - case None => Right(f) - } - ).partition(_.isRight) - } - - val checkedFlags = flags.collect { - case Right(flag) => flag - }.toSet - - val allErrors = (errors ++ errors2).collect { - case Left(error) => error - }.toList - - if !allErrors.isEmpty then Left(allErrors.mkString("\n")) else Right(SnippetCompilerArg(checkedFlags)) +object SCFlagsParser extends ArgParser[SCFlags]: + def parse(s: String): Either[String, SCFlags] = { + SCFlags.values + .find(_.flagName == s) + .fold(Left(s"$s: No such flag found."))(Right(_)) } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala index 31a263555a88..b2d7cb147146 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala @@ -59,7 +59,7 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetC info.split(" ") .find(_.startsWith("sc:")) .map(_.stripPrefix("sc:")) - .map(snippets.SnippetCompilerArgParser.parse) + .map(snippets.SCFlagsParser.parse) .flatMap(_.toOption) snippetCheckingFunc(node.getContentChars.toString, node.getStartLineNumber, argOverride) c.delegateRender() From e8a5df999a6d84a4926f42cf55712d7587ccced0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 29 Mar 2021 12:18:47 +0200 Subject: [PATCH 14/28] Move snippet compiler logic to parsing phase. --- .../scaladoc/renderers/DocRenderer.scala | 37 +++--------- .../scaladoc/renderers/HtmlRenderer.scala | 6 +- .../scaladoc/renderers/MemberRenderer.scala | 19 +++--- .../scaladoc/snippets/SnippetChecker.scala | 11 ---- .../snippets/SnippetCompilerArgs.scala | 8 +++ .../scaladoc/tasty/ScalaDocSupport.scala | 4 +- .../scaladoc/tasty/comments/Comments.scala | 60 ++++++++++++++++--- .../markdown/DocFlexmarkExtension.scala | 28 ++------- 8 files changed, 86 insertions(+), 87 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala index b9f567932da8..6f52abb0a61b 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/DocRenderer.scala @@ -6,40 +6,21 @@ import util.HTML._ import com.vladsch.flexmark.util.ast.{Node => MdNode} import dotty.tools.scaladoc.tasty.comments.wiki.WikiDocElement import dotty.tools.scaladoc.tasty.comments.markdown.DocFlexmarkRenderer -import dotty.tools.scaladoc.snippets._ -class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using ctx: DocContext): +class DocRender(signatureRenderer: SignatureRenderer)(using DocContext): - private def snippetCheckingFuncFromMember: Member => SnippetChecker.SnippetCheckingFunc = - (m: Member) => { - (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => { - val pathBasedArg = ctx.snippetCompilerArgs.get(m) - val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_)) - - snippetChecker.checkSnippet(str, m.docs.map(_.snippetCompilerData), arg, lineOffset).foreach { _ match { - case r: SnippetCompilationResult if !r.isSuccessful => - println(s"In member ${m.name} (${m.dri.location}):") - println(r.getSummary) - case _ => - } - } - } - } - - def renderDocPart(doc: DocPart)(using Member): AppliedTag = doc match + def renderDocPart(doc: DocPart): AppliedTag = doc match case md: MdNode => renderMarkdown(md) case Nil => raw("") case Seq(elem: WikiDocElement) => renderElement(elem) case list: Seq[WikiDocElement @unchecked] => div(list.map(renderElement)) - private def renderMarkdown(el: MdNode)(using m: Member): AppliedTag = - raw(DocFlexmarkRenderer.render(el)( - (link,name) => - renderLink(link, default => text(if name.isEmpty then default else name)).toString, - snippetCheckingFuncFromMember(m) + private def renderMarkdown(el: MdNode): AppliedTag = + raw(DocFlexmarkRenderer.render(el)( (link,name) => + renderLink(link, default => text(if name.isEmpty then default else name)).toString )) - private def listItems(items: Seq[WikiDocElement])(using m: Member) = + private def listItems(items: Seq[WikiDocElement]) = items.map(i => li(renderElement(i))) private def notSupported(name: String, content: AppliedTag): AppliedTag = report.warning(s"Wiki syntax does not support $name in ${signatureRenderer.currentDri.location}") @@ -54,7 +35,7 @@ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChe val tooltip = s"Problem linking $query: $msg" signatureRenderer.unresolvedLink(linkBody(query), titleAttr := tooltip) - private def renderElement(e: WikiDocElement)(using m: Member): AppliedTag = e match + private def renderElement(e: WikiDocElement): AppliedTag = e match case Title(text, level) => val content = renderElement(text) level match @@ -65,9 +46,7 @@ class DocRender(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChe case 5 => h5(content) case 6 => h6(content) case Paragraph(text) => p(renderElement(text)) - case Code(data: String) => - snippetCheckingFuncFromMember(m)(data, 0, None) - pre(code(raw(data))) // TODO add classes + case Code(data: String) => pre(code(raw(data))) // TODO add classes case HorizontalRule => hr case UnorderedList(items) => ul(listItems(items)) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala index 675d825e4cb8..8d79133a5e39 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala @@ -14,8 +14,6 @@ import java.nio.file.Files import java.nio.file.FileVisitOption import java.io.File -import dotty.tools.scaladoc.snippets._ - case class Page(link: Link, content: Member | ResolvedTemplate | String, children: Seq[Page]): def withNewChildren(newChildren: Seq[Page]) = copy(children = children ++ newChildren) @@ -28,7 +26,6 @@ case class Page(link: Link, content: Member | ResolvedTemplate | String, childre class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx: DocContext) extends SiteRenderer, Resources, Locations, Writer: private val args = summon[DocContext].args - private val snippetChecker = SnippetChecker() val staticSite = summon[DocContext].staticSiteContext val effectiveMembers = members @@ -78,7 +75,7 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx def link(dri: DRI): Option[String] = Some(pathToPage(currentDri, dri)).filter(_ != UnresolvedLocationLink) - MemberRenderer(signatureRenderer, snippetChecker).fullMember(m) + MemberRenderer(signatureRenderer).fullMember(m) case t: ResolvedTemplate => siteContent(page.link.dri, t) case a: String => raw(a) @@ -125,7 +122,6 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx def render(): Unit = val renderedResources = renderResources() val sites = allPages.map(renderPage(_, Vector.empty)) - println(snippetChecker.summary) def mkHead(page: Page): AppliedTag = val resources = page.content match diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala index bf32ffa9780d..2e58af5c3da9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala @@ -9,12 +9,11 @@ import dotty.tools.scaladoc.tasty.comments.markdown.DocFlexmarkRenderer import com.vladsch.flexmark.util.ast.{Node => MdNode} import dotty.tools.scaladoc.tasty.comments.wiki.WikiDocElement import translators._ -import dotty.tools.scaladoc.snippets._ -class MemberRenderer(signatureRenderer: SignatureRenderer, snippetChecker: SnippetChecker)(using DocContext) extends DocRender(signatureRenderer, snippetChecker): +class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) extends DocRender(signatureRenderer): import signatureRenderer._ - def doc(m: Member): Seq[AppliedTag] = m.docs.fold(Nil)(d => Seq(renderDocPart(d.body)(using m))) + def doc(m: Member): Seq[AppliedTag] = m.docs.fold(Nil)(d => Seq(renderDocPart(d.body))) def tableRow(name: String, content: AppliedTag) = Seq(dt(name), dd(content)) @@ -38,14 +37,14 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, snippetChecker: Snipp def nested(name: String, on: SortedMap[String, DocPart]): Seq[AppliedTag] = if on.isEmpty then Nil else tableRow(name, dl(cls := "attributes")( - on.map { case (name, value) => tableRow(name, renderDocPart(value)(using m))}.toList:_* + on.map { case (name, value) => tableRow(name, renderDocPart(value))}.toList:_* )) def list(name: String, on: List[DocPart]): Seq[AppliedTag] = - if on.isEmpty then Nil else tableRow(name, div(on.map(e => div(renderDocPart(e)(using m))))) + if on.isEmpty then Nil else tableRow(name, div(on.map(e => div(renderDocPart(e))))) def opt(name: String, on: Option[DocPart]): Seq[AppliedTag] = - if on.isEmpty then Nil else tableRow(name, renderDocPart(on.get)(using m)) + if on.isEmpty then Nil else tableRow(name, renderDocPart(on.get)) def authors(authors: List[DocPart]) = if summon[DocContext].args.includeAuthors then list("Authors", authors) else Nil @@ -90,14 +89,14 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, snippetChecker: Snipp Seq( since.map(s => code("[Since version ", parameter(s), "] ")), message.map(m => parameter(m))) - ++ m.docs.map(_.deprecated.toSeq.map(renderDocPart(_)(using m))) + ++ m.docs.map(_.deprecated.toSeq.map(renderDocPart)) ).flatten Seq(dt("Deprecated"), dd(content:_*)) } def memberInfo(m: Member, withBrief: Boolean = false): Seq[AppliedTag] = val comment = m.docs - val bodyContents = m.docs.fold(Nil)(e => renderDocPart(e.body)(using m) :: Nil) + val bodyContents = m.docs.fold(Nil)(e => renderDocPart(e.body) :: Nil) Seq( Option.when(withBrief)(div(cls := "documentableBrief doc")(comment.flatMap(_.short).fold("")(renderDocPart))), @@ -252,8 +251,8 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, snippetChecker: Snipp val rawGroups = membersInGroups.groupBy(_.docs.flatMap(_.group)).collect { case (Some(groupName), members) => ExpandedGroup( - names.get(groupName).fold(raw(groupName))(renderDocPart(_)(using m)), - descriptions.get(groupName).fold(raw(""))(renderDocPart(_)(using m)), + names.get(groupName).fold(raw(groupName))(renderDocPart), + descriptions.get(groupName).fold(raw(""))(renderDocPart), prios.getOrElse(groupName, 1000) ) -> members } diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index 3debdb0a116a..e7620d06e175 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -11,9 +11,6 @@ class SnippetChecker()(using ctx: DocContext): ctx.args.tastyDirs.map(_.getAbsolutePath()).mkString(sep) private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp) - private var warningsCount = 0 - private var errorsCount = 0 - def checkSnippet( snippet: String, data: Option[SnippetCompilerData], @@ -31,18 +28,10 @@ class SnippetChecker()(using ctx: DocContext): data.fold(0)(_.position.column) ) val res = compiler.compile(wrapped, arg) - if !res.isSuccessful && res.messages.exists(_.level == MessageLevel.Error) then errorsCount = errorsCount + 1 - if !res.isSuccessful && res.messages.exists(_.level == MessageLevel.Warning) then warningsCount = warningsCount + 1 Some(res) else None } - def summary: String = s""" - |Snippet compiler summary: - | Found $warningsCount warnings - | Found $errorsCount errors - |""".stripMargin - object SnippetChecker: type LineOffset = Int type SnippetCheckingFunc = (String, LineOffset, Option[SCFlags]) => Unit diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala index fd03f8683964..7dad71a4ea02 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala @@ -1,6 +1,8 @@ package dotty.tools.scaladoc package snippets +import java.nio.file.Path + case class SnippetCompilerArg(flag: SCFlags, debug: Boolean): def overrideFlag(f: SCFlags): SnippetCompilerArg = copy(flag = f) @@ -19,6 +21,12 @@ case class SnippetCompilerArgs(scFlags: PathBased[SCFlags], val debug: Boolean, .flatMap(s => scFlags.get(s.path).map(_.elem)) .fold(SnippetCompilerArg(defaultFlag, debug))(SnippetCompilerArg(_, debug)) + def get(path: Option[Path]): SnippetCompilerArg = + path + .flatMap(p => scFlags.get(p).map(_.elem)) + .fold(SnippetCompilerArg(defaultFlag, debug))(SnippetCompilerArg(_, debug)) + + object SnippetCompilerArgs: val usage = """ diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ScalaDocSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ScalaDocSupport.scala index 1c634df8c918..a48101b00e2d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ScalaDocSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ScalaDocSupport.scala @@ -29,9 +29,9 @@ object ScaladocSupport: val parser = commentSyntax match { case CommentSyntax.Wiki => - comments.WikiCommentParser(comments.Repr(quotes)(sym)) + comments.WikiCommentParser(comments.Repr(quotes)(sym), snippetChecker) case CommentSyntax.Markdown => - comments.MarkdownCommentParser(comments.Repr(quotes)(sym)) + comments.MarkdownCommentParser(comments.Repr(quotes)(sym), snippetChecker) } parser.parse(preparsed) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index 977e48689647..539de640fcc6 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -14,7 +14,7 @@ import dotty.tools.scaladoc.tasty.comments.wiki.Paragraph import dotty.tools.scaladoc.DocPart import dotty.tools.scaladoc.tasty.SymOpsWithLinkCache import collection.JavaConverters._ -import collection.JavaConverters._ +import dotty.tools.scaladoc.snippets._ class Repr(val qctx: Quotes)(val sym: qctx.reflect.Symbol) @@ -71,13 +71,14 @@ case class PreparsedComment( case class DokkaCommentBody(summary: Option[DocPart], body: DocPart) -abstract class MarkupConversion[T](val repr: Repr)(using DocContext) { +abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecker)(using dctx: DocContext) { protected def stringToMarkup(str: String): T protected def markupToDokka(t: T): DocPart protected def markupToString(t: T): String protected def markupToDokkaCommentBody(t: T): DokkaCommentBody protected def filterEmpty(xs: List[String]): List[T] protected def filterEmpty(xs: SortedMap[String, String]): SortedMap[String, T] + protected def processSnippets(t: T): T val qctx: repr.qctx.type = if repr == null then null else repr.qctx // TODO why we do need null? val owner: qctx.reflect.Symbol = @@ -175,8 +176,27 @@ abstract class MarkupConversion[T](val repr: Repr)(using DocContext) { } } + def snippetCheckingFunc: qctx.reflect.Symbol => SnippetChecker.SnippetCheckingFunc = + (s: qctx.reflect.Symbol) => { + val path = s.source.map(_.path) + val pathBasedArg = dctx.snippetCompilerArgs.get(path) + val data = getSnippetCompilerData(s) + (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => { + val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_)) + + snippetChecker.checkSnippet(str, Some(data), arg, lineOffset).foreach { _ match { + case r: SnippetCompilationResult if !r.isSuccessful => + val msg = s"In member ${s.name} (${s.dri.location}):\n${r.getSummary}" + report.error(msg)(using dctx.compilerContext) + case _ => + } + } + } + } + final def parse(preparsed: PreparsedComment): Comment = - val body = markupToDokkaCommentBody(stringToMarkup(preparsed.body)) + val markup = stringToMarkup(preparsed.body) + val body = markupToDokkaCommentBody(processSnippets(markup)) Comment( body = body.body, short = body.summary, @@ -202,8 +222,8 @@ abstract class MarkupConversion[T](val repr: Repr)(using DocContext) { ) } -class MarkdownCommentParser(repr: Repr)(using DocContext) - extends MarkupConversion[mdu.Node](repr) { +class MarkdownCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using DocContext) + extends MarkupConversion[mdu.Node](repr, snippetChecker) { def stringToMarkup(str: String) = MarkdownParser.parseToMarkdown(str, markdown.DocFlexmarkParser(resolveLink)) @@ -228,10 +248,32 @@ class MarkdownCommentParser(repr: Repr)(using DocContext) xs.view.mapValues(_.trim) .filterNot { case (_, v) => v.isEmpty } .mapValues(stringToMarkup).to(SortedMap) + + def processSnippets(root: mdu.Node): mdu.Node = { + val nodes = root.getDescendants().asScala.collect { + case fcb: mda.FencedCodeBlock => fcb + }.toList + if !nodes.isEmpty then { + val checkingFunc: SnippetChecker.SnippetCheckingFunc = snippetCheckingFunc(owner) + nodes.foreach { node => + val snippet = node.getContentChars.toString + val lineOffset = node.getStartLineNumber + val info = node.getInfo.toString + val argOverride = + info.split(" ") + .find(_.startsWith("sc:")) + .map(_.stripPrefix("sc:")) + .map(snippets.SCFlagsParser.parse) + .flatMap(_.toOption) + checkingFunc(snippet, lineOffset, argOverride) + } + } + root + } } -class WikiCommentParser(repr: Repr)(using DocContext) - extends MarkupConversion[wiki.Body](repr): +class WikiCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using DocContext) + extends MarkupConversion[wiki.Body](repr, snippetChecker): def stringToMarkup(str: String) = wiki.Parser(str, resolverLink).document() @@ -281,3 +323,7 @@ class WikiCommentParser(repr: Repr)(using DocContext) def filterEmpty(xs: SortedMap[String,String]) = xs.view.mapValues(stringToMarkup).to(SortedMap) .filterNot { case (_, v) => v.blocks.isEmpty } + + def processSnippets(root: wiki.Body): wiki.Body = + // Currently not supported + root diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala index b2d7cb147146..9cd0e0653898 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala @@ -10,9 +10,7 @@ import com.vladsch.flexmark.ext.wikilink.internal.WikiLinkLinkRefProcessor import com.vladsch.flexmark.util.ast._ import com.vladsch.flexmark.util.options._ import com.vladsch.flexmark.util.sequence.BasedSequence -import com.vladsch.flexmark._ -import dotty.tools.scaladoc.snippets.SnippetChecker class DocLinkNode( val target: DocLink, @@ -47,33 +45,17 @@ object DocFlexmarkParser { } } -case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetCheckingFunc: SnippetChecker.SnippetCheckingFunc) +case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String) extends HtmlRenderer.HtmlRendererExtension: - def rendererOptions(opt: MutableDataHolder): Unit = () // noop - object FencedCodeBlockHandler extends CustomNodeRenderer[ast.FencedCodeBlock]: - override def render(node: ast.FencedCodeBlock, c: NodeRendererContext, html: HtmlWriter): Unit = - val info = node.getInfo.toString - val argOverride = - info.split(" ") - .find(_.startsWith("sc:")) - .map(_.stripPrefix("sc:")) - .map(snippets.SCFlagsParser.parse) - .flatMap(_.toOption) - snippetCheckingFunc(node.getContentChars.toString, node.getStartLineNumber, argOverride) - c.delegateRender() - object Handler extends CustomNodeRenderer[DocLinkNode]: override def render(node: DocLinkNode, c: NodeRendererContext, html: HtmlWriter): Unit = html.raw(renderLink(node.target, node.body)) object Render extends NodeRenderer: override def getNodeRenderingHandlers: JSet[NodeRenderingHandler[_]] = - JSet( - new NodeRenderingHandler(classOf[DocLinkNode], Handler), - new NodeRenderingHandler(classOf[ast.FencedCodeBlock], FencedCodeBlockHandler) - ) + JSet(new NodeRenderingHandler(classOf[DocLinkNode], Handler)) object Factory extends NodeRendererFactory: override def create(options: DataHolder): NodeRenderer = Render @@ -82,6 +64,6 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String, snippetC htmlRendererBuilder.nodeRendererFactory(Factory) object DocFlexmarkRenderer: - def render(node: Node)(renderLink: (DocLink, String) => String, snippetCheckingFunc: SnippetChecker.SnippetCheckingFunc) = - val opts = MarkdownParser.mkMarkdownOptions(Seq(DocFlexmarkRenderer(renderLink, snippetCheckingFunc))) - HtmlRenderer.builder(opts).build().render(node) + def render(node: Node)(renderLink: (DocLink, String) => String) = + val opts = MarkdownParser.mkMarkdownOptions(Seq(DocFlexmarkRenderer(renderLink))) + HtmlRenderer.builder(opts).build().render(node) \ No newline at end of file From ac76ac6ed2b27dc4db08e4667c44ed405cfecc76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Tue, 30 Mar 2021 09:10:34 +0200 Subject: [PATCH 15/28] Refactor debug flag --- .../scaladoc/snippets/SnippetChecker.scala | 2 +- .../snippets/SnippetCompilationResult.scala | 1 + .../scaladoc/snippets/SnippetCompiler.scala | 9 ++------ .../scaladoc/tasty/comments/Comments.scala | 23 +++++++++++++------ 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index e7620d06e175..8d42cd86b192 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -34,4 +34,4 @@ class SnippetChecker()(using ctx: DocContext): object SnippetChecker: type LineOffset = Int - type SnippetCheckingFunc = (String, LineOffset, Option[SCFlags]) => Unit + type SnippetCheckingFunc = (String, LineOffset, Option[SCFlags]) => Option[SnippetCompilationResult] diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala index d4cdd1834357..2c5efba92e04 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala @@ -8,6 +8,7 @@ case class Position(line: Int, column: Int, sourceLine: String) case class SnippetCompilerMessage(position: Option[Position], message: String, level: MessageLevel) case class SnippetCompilationResult( + wrappedSnippet: String, isSuccessful: Boolean, result: Option[AbstractFile], messages: Seq[SnippetCompilerMessage] diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index bedea9f1dda1..222ca78f4991 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -61,14 +61,9 @@ class SnippetCompiler( } private def additionalMessages(wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg, context: Context): Seq[SnippetCompilerMessage] = { - ( Option.when(arg.flag == SCFlags.Fail && !context.reporter.hasErrors)( SnippetCompilerMessage(None, "Snippet should not compile but compiled succesfully", MessageLevel.Error) - ) ++ - Option.when(arg.debug && !isSuccessful(arg, context))( - SnippetCompilerMessage(None, s"\n${wrappedSnippet.snippet}", MessageLevel.Debug) - ) - ).toList + ).toList } private def isSuccessful(arg: SnippetCompilerArg, context: Context): Boolean = { @@ -94,5 +89,5 @@ class SnippetCompiler( additionalMessages(wrappedSnippet, arg, context) val t = Option.when(!context.reporter.hasErrors)(target) - SnippetCompilationResult(isSuccessful(arg, context), t, messages) + SnippetCompilationResult(wrappedSnippet.snippet, isSuccessful(arg, context), t, messages) } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index 539de640fcc6..c4028660abbc 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -4,7 +4,7 @@ package tasty.comments import scala.collection.immutable.SortedMap import scala.util.Try -import com.vladsch.flexmark.util.{ast => mdu} +import com.vladsch.flexmark.util.{ast => mdu, sequence} import com.vladsch.flexmark.{ast => mda} import com.vladsch.flexmark.formatter.Formatter import com.vladsch.flexmark.util.options.MutableDataSet @@ -40,8 +40,7 @@ case class Comment ( groupNames: SortedMap[String, DocPart], groupPrio: SortedMap[String, Int], /** List of conversions to hide - containing e.g: `scala.Predef.FloatArrayOps` */ - hideImplicitConversions: List[DocPart], - snippetCompilerData: SnippetCompilerData + hideImplicitConversions: List[DocPart] ) case class PreparsedComment( @@ -184,6 +183,7 @@ abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecke (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => { val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_)) + val res = snippetChecker.checkSnippet(str, Some(data), arg, lineOffset) snippetChecker.checkSnippet(str, Some(data), arg, lineOffset).foreach { _ match { case r: SnippetCompilationResult if !r.isSuccessful => val msg = s"In member ${s.name} (${s.dri.location}):\n${r.getSummary}" @@ -191,6 +191,7 @@ abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecke case _ => } } + res } } @@ -217,12 +218,11 @@ abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecke groupDesc = filterEmpty(preparsed.groupDesc).view.mapValues(markupToDokka).to(SortedMap), groupNames = filterEmpty(preparsed.groupNames).view.mapValues(markupToDokka).to(SortedMap), groupPrio = preparsed.groupPrio, - hideImplicitConversions = filterEmpty(preparsed.hideImplicitConversions).map(markupToDokka), - snippetCompilerData = getSnippetCompilerData(owner) + hideImplicitConversions = filterEmpty(preparsed.hideImplicitConversions).map(markupToDokka) ) } -class MarkdownCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using DocContext) +class MarkdownCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using dctx: DocContext) extends MarkupConversion[mdu.Node](repr, snippetChecker) { def stringToMarkup(str: String) = @@ -265,7 +265,16 @@ class MarkdownCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using Do .map(_.stripPrefix("sc:")) .map(snippets.SCFlagsParser.parse) .flatMap(_.toOption) - checkingFunc(snippet, lineOffset, argOverride) + checkingFunc(snippet, lineOffset, argOverride) match { + case Some(SnippetCompilationResult(wrapped, _, _, _)) if dctx.snippetCompilerArgs.debug => + val s = sequence.BasedSequence.EmptyBasedSequence() + .append(wrapped) + .append(sequence.BasedSequence.EOL) + val content = mdu.BlockContent() + content.add(s, 0) + node.setContent(content) + case _ => + } } } root From 94f77cb78889438923ad31aac8ca76c34c8e3140 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Thu, 25 Mar 2021 16:33:34 +0100 Subject: [PATCH 16/28] Display snippet compilation messages in scaladoc --- scaladoc-js/resources/scaladoc-searchbar.css | 38 ++--- .../code-snippets/CodeSnippets.scala | 16 --- .../src/tests/snippetCompilerTest.scala | 28 ++++ .../resources/dotty_res/styles/scalastyle.css | 39 +++++- .../snippets/SnippetCompilationResult.scala | 18 +-- .../scaladoc/snippets/SnippetCompiler.scala | 2 +- .../scaladoc/tasty/comments/Comments.scala | 68 +++++---- .../markdown/DocFlexmarkExtension.scala | 24 +++- .../comments/markdown/SnippetRenderer.scala | 130 ++++++++++++++++++ 9 files changed, 288 insertions(+), 75 deletions(-) create mode 100644 scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala diff --git a/scaladoc-js/resources/scaladoc-searchbar.css b/scaladoc-js/resources/scaladoc-searchbar.css index 7f708b338fe8..45f3b42080c8 100644 --- a/scaladoc-js/resources/scaladoc-searchbar.css +++ b/scaladoc-js/resources/scaladoc-searchbar.css @@ -105,27 +105,33 @@ .snippet-comment-button { position: absolute; + display: inline-block; left: 50%; width: 24px; height: 24px; -} - -.show-snippet-comments-button { - -ms-transform: translate(calc(-50% + 5em), -50%); - transform: translate(calc(-50% + 5em), -50%); - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Ccircle cx='12' cy='12' r='12' fill='white'/%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19 11.0053L19 13.1085H12.9873L12.988 19H10.8714L10.8721 13.1085L5 13.1085L5 11.0053L10.8721 11.0053V5L12.9865 5.00074L12.988 11.0045L19 11.0053Z' fill='%2327282C' fill-opacity='0.75'/%3E %3C/svg%3E"); -} - -.show-snippet-comments-button:hover { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Ccircle cx='12' cy='12' r='12' fill='light grey'/%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19 11.0053L19 13.1085H12.9873L12.988 19H10.8714L10.8721 13.1085L5 13.1085L5 11.0053L10.8721 11.0053V5L12.9865 5.00074L12.988 11.0045L19 11.0053Z' fill='%2327282C' fill-opacity='0.75'/%3E %3C/svg%3E"); + background: + linear-gradient(#fff, #fff), + linear-gradient(#fff, #fff), + #aaa; + background-position: center; + background-size: 50% 2px, 2px 50%; + background-repeat: no-repeat; + border-radius: 12px; + box-shadow: 0 0 2px black; +} + +.snippet-comment-button:hover { + background: + linear-gradient(#444, #444), + linear-gradient(#444, #444), + #ddd; + background-position: center; + background-size: 50% 2px, 2px 50%; + background-repeat: no-repeat; } .hide-snippet-comments-button { - -ms-transform: translate(calc(-50% + 5em), -50%) rotate(45deg); - transform: translate(calc(-50% + 5em), -50%) rotate(45deg); - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Ccircle cx='12' cy='12' r='12' fill='white'/%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19 11.0053L19 13.1085H12.9873L12.988 19H10.8714L10.8721 13.1085L5 13.1085L5 11.0053L10.8721 11.0053V5L12.9865 5.00074L12.988 11.0045L19 11.0053Z' fill='%2327282C' fill-opacity='0.75'/%3E %3C/svg%3E"); + -ms-transform: rotate(45deg); + transform: rotate(45deg); } -.hide-snippet-comments-button:hover { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Ccircle cx='12' cy='12' r='12' fill='light grey'/%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19 11.0053L19 13.1085H12.9873L12.988 19H10.8714L10.8721 13.1085L5 13.1085L5 11.0053L10.8721 11.0053V5L12.9865 5.00074L12.988 11.0045L19 11.0053Z' fill='%2327282C' fill-opacity='0.75'/%3E %3C/svg%3E"); -} diff --git a/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala b/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala index b6ed3d476887..50f0ad2d5df7 100644 --- a/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala +++ b/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala @@ -4,19 +4,6 @@ import org.scalajs.dom._ import org.scalajs.dom.ext._ class CodeSnippets: - val replacePatternsAndResults: Seq[(String, String)] = Seq( - """(\/\/{{\n)((.|\n)*?)(\/\/}}\n)""" -> """$2""", // wrap content of block directives - """(\/\/.*?)(\n|\$)""" -> """$1$2""", // wrap single line comment - """(\/\*)((.|\n)*?)(\*\/)""" -> """$0""", // wrap multi line comment - ) - - document.querySelectorAll("code").foreach { - case e: html.Element => e.innerHTML = replacePatternsAndResults.foldLeft(e.innerHTML) { - case (acc, (pattern, result)) => - acc.replaceAll(pattern, result) - } - } - def toggleHide(e: html.Element | html.Document) = e.querySelectorAll("code span.hideable").foreach { case e: html.Element if e.style.getPropertyValue("display").isEmpty => e.style.setProperty("display", "none") case e: html.Element => e.style.removeProperty("display") @@ -29,15 +16,12 @@ class CodeSnippets: val a = document.createElement("a") a.addEventListener("click", { (_: MouseEvent) => if(a.classList.contains("hide-snippet-comments-button")) { - a.classList.add("show-snippet-comments-button") a.classList.remove("hide-snippet-comments-button") } else { a.classList.add("hide-snippet-comments-button") - a.classList.remove("show-snippet-comments-button") } toggleHide(e) }) a.classList.add("snippet-comment-button") - a.classList.add("show-snippet-comments-button") e.insertBefore(a, e.firstChild) } diff --git a/scaladoc-testcases/src/tests/snippetCompilerTest.scala b/scaladoc-testcases/src/tests/snippetCompilerTest.scala index 0cce0dd6e65b..dcc32cbcc649 100644 --- a/scaladoc-testcases/src/tests/snippetCompilerTest.scala +++ b/scaladoc-testcases/src/tests/snippetCompilerTest.scala @@ -2,9 +2,29 @@ package snippetCompiler /** * ```scala sc:compile + * //{ + * import scala.collection.Seq + * //} + * * def a = 2 * val x = 1 + List() * a + * + * try { + * 2+3 + * } + * + * /* + * Huge comment + * */ + * val xd: String = 42 + * + * def a(i: Int): Boolean = i match // This is a function + * case 1 => true + * + * val b: Int = 2.3 /* Also quite a big comment */ + * + * val d: Long = "asd" * ``` * * ```scala sc:failing @@ -23,3 +43,11 @@ package snippetCompiler * ``` */ class A { } + +/** + * + * ```scala sc:compile + * val c: Int = 4.5 + * ``` + */ +class B { } \ No newline at end of file diff --git a/scaladoc/resources/dotty_res/styles/scalastyle.css b/scaladoc/resources/dotty_res/styles/scalastyle.css index 99a5996781f1..610b7b6b1d93 100644 --- a/scaladoc/resources/dotty_res/styles/scalastyle.css +++ b/scaladoc/resources/dotty_res/styles/scalastyle.css @@ -93,15 +93,47 @@ code { padding: 0 .3em; } pre { - overflow-x: auto; scrollbar-width: thin; + margin: 0px; } pre code, pre code.hljs { font-size: 1em; padding: 0; } -pre, .symbol.monospace { +.snippet { padding: 12px 8px 10px 12px; + background: var(--code-bg); + margin: 1em 0px; + border-radius: 2px; + box-shadow: 0 0 2px #888; +} +.snippet-error { + border-bottom: 2px dotted red; +} +.snippet-warn { + border-bottom: 2px dotted orange; +} +.snippet-info { + border-bottom: 2px dotted teal; +} +.snippet-debug { + border-bottom: 2px dotted pink; +} +.tooltip { + position: relative; +} +.tooltip:hover:after { + content: attr(label); + padding: 4px 8px; + color: white; + background-color:black; + position: absolute; + left: 0; + z-index:10; + box-shadow:0 0 3px #444; + opacity: 0.8; +} +pre, .symbol.monospace { font-weight: 500; font-size: 12px; } @@ -109,6 +141,9 @@ pre .hljs-comment { /* Fold comments in snippets */ white-space: normal; } +.symbol.monospace { + padding: 12px 8px 10px 12px; +} a, a:visited, span[data-unresolved-link] { text-decoration: none; color: var(--link-fg); diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala index 2c5efba92e04..d44a7fb23b8f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala @@ -3,9 +3,13 @@ package snippets import dotty.tools.io.{ AbstractFile } -case class Position(line: Int, column: Int, sourceLine: String) +case class Position(line: Int, column: Int, sourceLine: String, relativeLine: Int) -case class SnippetCompilerMessage(position: Option[Position], message: String, level: MessageLevel) +case class SnippetCompilerMessage(position: Option[Position], message: String, level: MessageLevel): + def getSummary: String = + position.fold(s"${level.text}: ${message}") { pos => + s"At ${pos.line}:${pos.column}:\n${pos.sourceLine}${level.text}: ${message}" + } case class SnippetCompilationResult( wrappedSnippet: String, @@ -13,15 +17,7 @@ case class SnippetCompilationResult( result: Option[AbstractFile], messages: Seq[SnippetCompilerMessage] ): - def getSummary: String = - messages.map(m => - m.position.fold( - s"${m.level.text}: ${m.message}" - )(pos => - s"At ${pos.line}:${pos.column}:\n${pos.sourceLine}${m.level.text}: ${m.message}" - ) - ).mkString("\n") - + def getSummary: String = messages.map(_.getSummary).mkString("\n") enum MessageLevel(val text: String): case Info extends MessageLevel("Info") diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index 222ca78f4991..1e76b0df9dfb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -48,7 +48,7 @@ class SnippetCompiler( case diagnostic if diagnostic.position.isPresent => val diagPos = diagnostic.position.get val pos = Some( - Position(diagPos.line + line, diagPos.column + column, diagPos.lineContent) + Position(diagPos.line + line, diagPos.column + column, diagPos.lineContent, diagPos.line) ) val msg = nullableMessage(diagnostic.message) val level = MessageLevel.fromOrdinal(diagnostic.level) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index c4028660abbc..05caa3c6d7c8 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -10,6 +10,7 @@ import com.vladsch.flexmark.formatter.Formatter import com.vladsch.flexmark.util.options.MutableDataSet import scala.quoted._ +import dotty.tools.scaladoc.tasty.comments.markdown.ExtendedFencedCodeBlock import dotty.tools.scaladoc.tasty.comments.wiki.Paragraph import dotty.tools.scaladoc.DocPart import dotty.tools.scaladoc.tasty.SymOpsWithLinkCache @@ -183,15 +184,13 @@ abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecke (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => { val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_)) - val res = snippetChecker.checkSnippet(str, Some(data), arg, lineOffset) - snippetChecker.checkSnippet(str, Some(data), arg, lineOffset).foreach { _ match { + snippetChecker.checkSnippet(str, Some(data), arg, lineOffset).collect { case r: SnippetCompilationResult if !r.isSuccessful => val msg = s"In member ${s.name} (${s.dri.location}):\n${r.getSummary}" report.error(msg)(using dctx.compilerContext) - case _ => - } + r + case r => r } - res } } @@ -253,29 +252,44 @@ class MarkdownCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using dc val nodes = root.getDescendants().asScala.collect { case fcb: mda.FencedCodeBlock => fcb }.toList - if !nodes.isEmpty then { - val checkingFunc: SnippetChecker.SnippetCheckingFunc = snippetCheckingFunc(owner) - nodes.foreach { node => - val snippet = node.getContentChars.toString - val lineOffset = node.getStartLineNumber - val info = node.getInfo.toString - val argOverride = - info.split(" ") - .find(_.startsWith("sc:")) - .map(_.stripPrefix("sc:")) - .map(snippets.SCFlagsParser.parse) - .flatMap(_.toOption) - checkingFunc(snippet, lineOffset, argOverride) match { - case Some(SnippetCompilationResult(wrapped, _, _, _)) if dctx.snippetCompilerArgs.debug => - val s = sequence.BasedSequence.EmptyBasedSequence() - .append(wrapped) - .append(sequence.BasedSequence.EOL) - val content = mdu.BlockContent() - content.add(s, 0) - node.setContent(content) - case _ => - } + val checkingFunc: SnippetChecker.SnippetCheckingFunc = snippetCheckingFunc(owner) + nodes.foreach { node => + val snippet = node.getContentChars.toString + val lineOffset = node.getStartLineNumber + val info = node.getInfo.toString + val argOverride = + info.split(" ") + .find(_.startsWith("sc:")) + .map(_.stripPrefix("sc:")) + .map(snippets.SCFlagsParser.parse) + .flatMap(_.toOption) + val snippetCompilationResult = checkingFunc(snippet, lineOffset, argOverride) match { + case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if dctx.snippetCompilerArgs.debug => + val s = sequence.BasedSequence.EmptyBasedSequence() + .append(wrapped) + .append(sequence.BasedSequence.EOL) + val content = mdu.BlockContent() + content.add(s, 0) + node.setContent(content) + result + case result => + // result.modify(_.each.messages.each.position.each.relativeLine).using(_ - 2) + result.map { r => + r.copy( + messages = r.messages.map { m => + m.copy( + position = m.position.map { p => + p.copy( + relativeLine = p.relativeLine - 2 + ) + } + ) + } + ) + } } + node.insertBefore(new ExtendedFencedCodeBlock(node, snippetCompilationResult)) + node.unlink() } root } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala index 9cd0e0653898..7938ed571349 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala @@ -10,13 +10,21 @@ import com.vladsch.flexmark.ext.wikilink.internal.WikiLinkLinkRefProcessor import com.vladsch.flexmark.util.ast._ import com.vladsch.flexmark.util.options._ import com.vladsch.flexmark.util.sequence.BasedSequence +import com.vladsch.flexmark._ +import dotty.tools.scaladoc.snippets._ +import scala.collection.JavaConverters._ class DocLinkNode( val target: DocLink, val body: String, seq: BasedSequence - ) extends WikiNode(seq, false, false, false, false) +) extends WikiNode(seq, false, false, false, false) + +case class ExtendedFencedCodeBlock( + codeBlock: ast.FencedCodeBlock, + compilationResult: Option[SnippetCompilationResult] +) extends WikiNode(codeBlock.getChars, false, false, false, false) class DocFlexmarkParser(resolveLink: String => DocLink) extends Parser.ParserExtension: @@ -49,13 +57,25 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String) extends HtmlRenderer.HtmlRendererExtension: def rendererOptions(opt: MutableDataHolder): Unit = () // noop + object ExtendedFencedCodeBlockHandler extends CustomNodeRenderer[ExtendedFencedCodeBlock]: + override def render(node: ExtendedFencedCodeBlock, c: NodeRendererContext, html: HtmlWriter): Unit = + html.raw( + SnippetRenderer.renderSnippetWithMessages( + node.codeBlock.getContentChars.toString.split("\n").map(_ + "\n").toSeq, + node.compilationResult.toSeq.flatMap(_.messages) + ) + ) + object Handler extends CustomNodeRenderer[DocLinkNode]: override def render(node: DocLinkNode, c: NodeRendererContext, html: HtmlWriter): Unit = html.raw(renderLink(node.target, node.body)) object Render extends NodeRenderer: override def getNodeRenderingHandlers: JSet[NodeRenderingHandler[_]] = - JSet(new NodeRenderingHandler(classOf[DocLinkNode], Handler)) + JSet( + new NodeRenderingHandler(classOf[DocLinkNode], Handler), + new NodeRenderingHandler(classOf[ExtendedFencedCodeBlock], ExtendedFencedCodeBlockHandler), + ) object Factory extends NodeRendererFactory: override def create(options: DataHolder): NodeRenderer = Render diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala new file mode 100644 index 000000000000..0d9f067e0785 --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala @@ -0,0 +1,130 @@ +package dotty.tools.scaladoc.tasty.comments.markdown + +import com.vladsch.flexmark.html._ + +import dotty.tools.scaladoc.snippets._ + +case class SnippetLine(content: String, lineNo: Int, classes: Set[String] = Set.empty, messages: Seq[String] = Seq.empty): + def withClass(cls: String) = this.copy(classes = classes + cls) + private def escapeQuotes(msg: String): String = msg.flatMap { + case '"' => """ + case c => c.toString + } + def toHTML = + val label = if messages.nonEmpty then s"""label="${messages.map(escapeQuotes).mkString("\n")}"""" else "" + s"""$content""" + +object SnippetRenderer: + private def compileMessageCSSClass(msg: SnippetCompilerMessage) = msg.level match + case MessageLevel.Info => "snippet-info" + case MessageLevel.Warning => "snippet-warn" + case MessageLevel.Error => "snippet-error" + case MessageLevel.Debug => "snippet-debug" + + private def cutBetwenSymbols[A]( + startSymbol: String, + endSymbol: String, + snippetLines: Seq[SnippetLine] + )( + f: (Seq[SnippetLine], Seq[SnippetLine], Seq[SnippetLine]) => A + ): Option[A] = + for { + startIdx <- snippetLines.zipWithIndex.find(_._1.content.contains(startSymbol)).map(_._2) + endIdx <- snippetLines.zipWithIndex.find(_._1.content.contains(endSymbol)).map(_._2) + (tmp, end) = snippetLines.splitAt(endIdx+1) + (begin, mid) = tmp.splitAt(startIdx) + } yield f(begin, mid, end) + + private def wrapHiddenSymbols(snippetLines: Seq[SnippetLine]): Seq[SnippetLine] = + val mRes = cutBetwenSymbols("//{", "//}", snippetLines) { + case (begin, mid, end) => + begin ++ mid.drop(1).dropRight(1).map(_.withClass("hideable")) ++ wrapHiddenSymbols(end) + } + mRes.getOrElse(snippetLines) + + private def wrapLineInBetween(startSymbol: Option[String], endSymbol: Option[String], line: SnippetLine): SnippetLine = + val startIdx = startSymbol.map(s => line.content.indexOf(s)) + val endIdx = endSymbol.map(s => line.content.indexOf(s)) + (startIdx, endIdx) match + case (Some(idx), None) => + val (code, comment) = line.content.splitAt(idx) + comment match + case _ if code.forall(_.isWhitespace) => + line.withClass("hideable") + case _ if comment.last == '\n' => + line.copy(content = code + s"""${comment.dropRight(1)}${"\n"}""") + case _ => + line.copy(content = code + s"""$comment""") + case (None, Some(idx)) => + val (comment, code) = line.content.splitAt(idx+endSymbol.get.size) + comment match + case _ if code.forall(_.isWhitespace) => + line.withClass("hideable") + case _ => + line.copy(content = s"""$comment""" + code) + case (Some(startIdx), Some(endIdx)) => + val (tmp, end) = line.content.splitAt(endIdx+endSymbol.get.size) + val (begin, comment) = tmp.splitAt(startIdx) + line.copy(content = begin + s"""$comment""" + end) + case _ => line + + private def wrapSingleLineComments(snippetLines: Seq[SnippetLine]): Seq[SnippetLine] = + snippetLines.map { line => + line.content.indexOf("//") match + case -1 => line + case idx => + wrapLineInBetween(Some("//"), None, line) + } + + private def wrapMultiLineComments(snippetLines: Seq[SnippetLine]): Seq[SnippetLine] = + val mRes = cutBetwenSymbols("/*", "*/", snippetLines) { + case (begin, mid, end) if mid.size == 1 => + val midRedacted = mid.map(wrapLineInBetween(Some("/*"), Some("*/"), _)) + begin ++ midRedacted ++ end + case (begin, mid, end) => + val midRedacted = + mid.take(1).map(wrapLineInBetween(Some("/*"), None, _)) + ++ mid.drop(1).dropRight(1).map(_.withClass("hideable")) + ++ mid.takeRight(1).map(wrapLineInBetween(None, Some("*/"), _)) + begin ++ midRedacted ++ wrapMultiLineComments(end) + } + mRes.getOrElse(snippetLines) + + private def wrapCodeLines(codeLines: Seq[String]): Seq[SnippetLine] = + val snippetLines = codeLines.zipWithIndex.map { + case (content, idx) => SnippetLine(content, idx) + } + wrapHiddenSymbols + .andThen(wrapSingleLineComments) + .andThen(wrapMultiLineComments) + .apply(snippetLines) + + private def addCompileMessages(messages: Seq[SnippetCompilerMessage])(codeLines: Seq[SnippetLine]): Seq[SnippetLine] = //TODO add tooltips and stuff + val messagesDict = messages.filter(_.position.nonEmpty).groupBy(_.position.get.relativeLine).toMap[Int, Seq[SnippetCompilerMessage]] + codeLines.map { line => + messagesDict.get(line.lineNo) match + case None => line + case Some(messages) => + val classes = List( + messages.find(_.level == MessageLevel.Error).map(compileMessageCSSClass), + messages.find(_.level == MessageLevel.Warning).map(compileMessageCSSClass), + messages.find(_.level == MessageLevel.Info).map(compileMessageCSSClass) + ).flatten + line.copy(classes = line.classes ++ classes.toSet ++ Set("tooltip"), messages = messages.map(_.message)) + } + + private def messagesHTML(messages: Seq[SnippetCompilerMessage]): String = + if messages.isEmpty + then "" + else + val content = messages + .map { msg => + s"""${msg.getSummary}""" + } + .mkString("
") + s"""
$content""" + + def renderSnippetWithMessages(codeLines: Seq[String], messages: Seq[SnippetCompilerMessage]): String = + val transformedLines = wrapCodeLines.andThen(addCompileMessages(messages)).apply(codeLines).map(_.toHTML) + val codeHTML = s"""${transformedLines.mkString("")}
""" + s"""
$codeHTML
""" \ No newline at end of file From bcea26828ef124b85982f36fe4efd4181d5cdc96 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Thu, 8 Apr 2021 12:08:04 +0200 Subject: [PATCH 17/28] Apply review changes --- .../scaladoc/tasty/comments/Comments.scala | 75 ++++++++++--------- .../comments/markdown/SnippetRenderer.scala | 5 +- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index 05caa3c6d7c8..2b6dfed6bce9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -252,44 +252,45 @@ class MarkdownCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using dc val nodes = root.getDescendants().asScala.collect { case fcb: mda.FencedCodeBlock => fcb }.toList - val checkingFunc: SnippetChecker.SnippetCheckingFunc = snippetCheckingFunc(owner) - nodes.foreach { node => - val snippet = node.getContentChars.toString - val lineOffset = node.getStartLineNumber - val info = node.getInfo.toString - val argOverride = - info.split(" ") - .find(_.startsWith("sc:")) - .map(_.stripPrefix("sc:")) - .map(snippets.SCFlagsParser.parse) - .flatMap(_.toOption) - val snippetCompilationResult = checkingFunc(snippet, lineOffset, argOverride) match { - case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if dctx.snippetCompilerArgs.debug => - val s = sequence.BasedSequence.EmptyBasedSequence() - .append(wrapped) - .append(sequence.BasedSequence.EOL) - val content = mdu.BlockContent() - content.add(s, 0) - node.setContent(content) - result - case result => - // result.modify(_.each.messages.each.position.each.relativeLine).using(_ - 2) - result.map { r => - r.copy( - messages = r.messages.map { m => - m.copy( - position = m.position.map { p => - p.copy( - relativeLine = p.relativeLine - 2 - ) - } - ) - } - ) - } + if nodes.nonEmpty then { + val checkingFunc: SnippetChecker.SnippetCheckingFunc = snippetCheckingFunc(owner) + nodes.foreach { node => + val snippet = node.getContentChars.toString + val lineOffset = node.getStartLineNumber + val info = node.getInfo.toString + val argOverride = + info.split(" ") + .find(_.startsWith("sc:")) + .map(_.stripPrefix("sc:")) + .map(snippets.SCFlagsParser.parse) + .flatMap(_.toOption) + val snippetCompilationResult = checkingFunc(snippet, lineOffset, argOverride) match { + case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if dctx.snippetCompilerArgs.debug => + val s = sequence.BasedSequence.EmptyBasedSequence() + .append(wrapped) + .append(sequence.BasedSequence.EOL) + val content = mdu.BlockContent() + content.add(s, 0) + node.setContent(content) + result + case result => + result.map { r => + r.copy( + messages = r.messages.map { m => + m.copy( + position = m.position.map { p => + p.copy( + relativeLine = p.relativeLine - lineOffset + ) + } + ) + } + ) + } + } + node.insertBefore(new ExtendedFencedCodeBlock(node, snippetCompilationResult)) + node.unlink() } - node.insertBefore(new ExtendedFencedCodeBlock(node, snippetCompilationResult)) - node.unlink() } root } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala index 0d9f067e0785..39fe63f8a3b3 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala @@ -6,10 +6,7 @@ import dotty.tools.scaladoc.snippets._ case class SnippetLine(content: String, lineNo: Int, classes: Set[String] = Set.empty, messages: Seq[String] = Seq.empty): def withClass(cls: String) = this.copy(classes = classes + cls) - private def escapeQuotes(msg: String): String = msg.flatMap { - case '"' => """ - case c => c.toString - } + private def escapeQuotes(msg: String): String = msg.replace("\"", """) def toHTML = val label = if messages.nonEmpty then s"""label="${messages.map(escapeQuotes).mkString("\n")}"""" else "" s"""$content""" From 8ddb2988d7cd87277a5367a848a35c686118260f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 9 Apr 2021 11:29:21 +0200 Subject: [PATCH 18/28] Run snippet compiler on static sites --- scaladoc-testcases/docs/docs/index.md | 9 +++ .../src/tests/snippetCompilerTest.scala | 5 +- .../src/dotty/tools/scaladoc/DocContext.scala | 7 ++- .../tools/scaladoc/site/LoadedTemplate.scala | 2 +- .../scaladoc/site/StaticSiteContext.scala | 4 +- .../dotty/tools/scaladoc/site/common.scala | 3 +- .../dotty/tools/scaladoc/site/templates.scala | 28 +++++++-- .../snippets/FlexmarkSnippetProcessor.scala | 47 +++++++++++++++ .../scaladoc/snippets/SnippetChecker.scala | 12 ++-- .../scaladoc/snippets/SnippetCompiler.scala | 9 ++- .../snippets/SnippetCompilerArgs.scala | 4 +- .../scaladoc/snippets/WrappedSnippet.scala | 10 ++-- .../scaladoc/tasty/ScalaDocSupport.scala | 4 +- .../scaladoc/tasty/comments/Comments.scala | 60 +++---------------- .../markdown/DocFlexmarkExtension.scala | 17 ++---- .../markdown/SnippetRenderingExtension.scala | 37 ++++++++++++ 16 files changed, 169 insertions(+), 89 deletions(-) create mode 100644 scaladoc-testcases/docs/docs/index.md create mode 100644 scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala create mode 100644 scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderingExtension.scala diff --git a/scaladoc-testcases/docs/docs/index.md b/scaladoc-testcases/docs/docs/index.md new file mode 100644 index 000000000000..f0465b1067c2 --- /dev/null +++ b/scaladoc-testcases/docs/docs/index.md @@ -0,0 +1,9 @@ +--- + + +--- + +```scala sc:compile +2 + List(0) +``` + diff --git a/scaladoc-testcases/src/tests/snippetCompilerTest.scala b/scaladoc-testcases/src/tests/snippetCompilerTest.scala index dcc32cbcc649..de77984d1a81 100644 --- a/scaladoc-testcases/src/tests/snippetCompilerTest.scala +++ b/scaladoc-testcases/src/tests/snippetCompilerTest.scala @@ -27,13 +27,13 @@ package snippetCompiler * val d: Long = "asd" * ``` * - * ```scala sc:failing + * ```scala sc:fail * def a = 2 * val x = 1 + List() * a * ``` * - * ```scala sc:failing + * ```scala sc:fail * def a = 2 * ``` * @@ -45,7 +45,6 @@ package snippetCompiler class A { } /** - * * ```scala sc:compile * val c: Int = 4.5 * ``` diff --git a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala index af1f40499142..02acf651b127 100644 --- a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala @@ -76,10 +76,15 @@ case class DocContext(args: Scaladoc.Args, compilerContext: CompilerContext): lazy val snippetCompilerArgs = snippets.SnippetCompilerArgs.load(args.snippetCompiler, args.snippetCompilerDebug)(using compilerContext) + lazy val snippetChecker = snippets.SnippetChecker(args.classpath, args.tastyDirs) + lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext( File(path).getAbsoluteFile(), args, - sourceLinks + sourceLinks, + snippetCompilerArgs, + snippetChecker )(using compilerContext)) + val externalDocumentationLinks = args.externalMappings diff --git a/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala b/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala index beda1eb14676..590a016a25d3 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala @@ -51,4 +51,4 @@ case class LoadedTemplate( ("site" -> (getMap("site") + ("posts" -> posts))) + ("urls" -> sourceLinks.toMap) + ("page" -> (getMap("page") + ("title" -> templateFile.title))) - templateFile.resolveInner(RenderingContext(updatedSettings, ctx.layouts)) + templateFile.resolveInner(RenderingContext(updatedSettings, ctx.layouts))(using ctx) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala index 8a2d60036e6c..010ecfeec052 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala @@ -13,7 +13,9 @@ import collection.JavaConverters._ class StaticSiteContext( val root: File, val args: Scaladoc.Args, - val sourceLinks: SourceLinks)(using val outerCtx: CompilerContext): + val sourceLinks: SourceLinks, + val snippetCompilerArgs: snippets.SnippetCompilerArgs, + val snippetChecker: snippets.SnippetChecker)(using val outerCtx: CompilerContext): var memberLinkResolver: String => Option[DRI] = _ => None diff --git a/scaladoc/src/dotty/tools/scaladoc/site/common.scala b/scaladoc/src/dotty/tools/scaladoc/site/common.scala index 8cc576ad82d4..733ffa994afc 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/common.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/common.scala @@ -36,7 +36,8 @@ val defaultMarkdownOptions: DataHolder = EmojiExtension.create(), YamlFrontMatterExtension.create(), StrikethroughExtension.create(), - WikiLinkExtension.create() + WikiLinkExtension.create(), + tasty.comments.markdown.SnippetRenderingExtension )) def emptyTemplate(file: File, title: String): TemplateFile = TemplateFile( diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index 5ff9fd161746..96dab5efa6cb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -2,7 +2,7 @@ package dotty.tools.scaladoc package site import java.io.File -import java.nio.file.Files +import java.nio.file.{Files, Paths} import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension import com.vladsch.flexmark.ext.autolink.AutolinkExtension @@ -18,6 +18,7 @@ import liqp.Template import scala.collection.JavaConverters._ import scala.io.Source +import dotty.tools.scaladoc.snippets._ case class RenderingContext( properties: Map[String, Object], @@ -55,7 +56,22 @@ case class TemplateFile( ): def isIndexPage() = file.isFile && (file.getName == "index.md" || file.getName == "index.html") - private[site] def resolveInner(ctx: RenderingContext): ResolvedPage = + private[site] def resolveInner(ctx: RenderingContext)(using ssctx: StaticSiteContext): ResolvedPage = + + lazy val snippetCheckingFunc: SnippetChecker.SnippetCheckingFunc = + val path = Some(Paths.get(file.getAbsolutePath)) + val pathBasedArg = ssctx.snippetCompilerArgs.get(path) + (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => { + val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_)) + ssctx.snippetChecker.checkSnippet(str, None, arg, lineOffset).collect { + case r: SnippetCompilationResult if !r.isSuccessful => + val msg = s"In static site (${file.getAbsolutePath}):\n${r.getSummary}" + report.error(msg)(using ssctx.outerCtx) + r + case r => r + } + } + if (ctx.resolving.contains(file.getAbsolutePath)) throw new RuntimeException(s"Cycle in templates involving $file: ${ctx.resolving}") @@ -74,9 +90,13 @@ case class TemplateFile( val rendered = Template.parse(this.rawCode).render(mutableProperties) // We want to render markdown only if next template is html val code = if (isHtml || layoutTemplate.exists(!_.isHtml)) rendered else + // Snippet compiler currently supports markdown only val parser: Parser = Parser.builder(defaultMarkdownOptions).build() - HtmlRenderer.builder(defaultMarkdownOptions).build().render(parser.parse(rendered)) + val parsedMd = parser.parse(rendered) + val processed = FlexmarkSnippetProcessor.processSnippets(parsedMd, ssctx.snippetCompilerArgs.debug, snippetCheckingFunc) + HtmlRenderer.builder(defaultMarkdownOptions).build().render(processed) + layoutTemplate match case None => ResolvedPage(code, resources ++ ctx.resources) case Some(layoutTemplate) => - layoutTemplate.resolveInner(ctx.nest(code, file, resources)) + layoutTemplate.resolveInner(ctx.nest(code, file, resources)) \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala new file mode 100644 index 000000000000..3cbd040efea2 --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala @@ -0,0 +1,47 @@ +package dotty.tools.scaladoc +package snippets + +import com.vladsch.flexmark.util.{ast => mdu, sequence} +import com.vladsch.flexmark.{ast => mda} +import com.vladsch.flexmark.formatter.Formatter +import com.vladsch.flexmark.util.options.MutableDataSet +import collection.JavaConverters._ + +import dotty.tools.scaladoc.tasty.comments.markdown.ExtendedFencedCodeBlock + +object FlexmarkSnippetProcessor: + def processSnippets(root: mdu.Node, debug: Boolean, checkingFunc: => SnippetChecker.SnippetCheckingFunc): mdu.Node = { + lazy val cf: SnippetChecker.SnippetCheckingFunc = checkingFunc + + val nodes = root.getDescendants().asScala.collect { + case fcb: mda.FencedCodeBlock => fcb + }.toList + + nodes.foreach { node => + val snippet = node.getContentChars.toString + val lineOffset = node.getStartLineNumber + val info = node.getInfo.toString + val argOverride = + info.split(" ") + .find(_.startsWith("sc:")) + .map(_.stripPrefix("sc:")) + .map(SCFlagsParser.parse) + .flatMap(_.toOption) + val snippetCompilationResult = cf(snippet, lineOffset, argOverride) match { + case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if debug => + val s = sequence.BasedSequence.EmptyBasedSequence() + .append(wrapped) + .append(sequence.BasedSequence.EOL) + val content = mdu.BlockContent() + content.add(s, 0) + node.setContent(content) + result + case result => result + } + + node.insertBefore(new ExtendedFencedCodeBlock(node, snippetCompilationResult)) + node.unlink() + } + + root + } \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index 8d42cd86b192..56901760ccec 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -3,12 +3,16 @@ package snippets import dotty.tools.scaladoc.DocContext import java.nio.file.Paths +import java.io.File -class SnippetChecker()(using ctx: DocContext): +class SnippetChecker(val classpath: String, val tastyDirs: Seq[File]): private val sep = System.getProperty("path.separator") - private val cp = System.getProperty("java.class.path") + sep + - Paths.get(ctx.args.classpath).toAbsolutePath + sep + - ctx.args.tastyDirs.map(_.getAbsolutePath()).mkString(sep) + private val cp = List( + System.getProperty("java.class.path"), + Paths.get(classpath).toAbsolutePath, + tastyDirs.map(_.getAbsolutePath()).mkString(sep) + ).mkString(sep) + private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp) def checkSnippet( diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index 1e76b0df9dfb..34d2c8dd982e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -42,13 +42,16 @@ class SnippetCompiler( private def nullableMessage(msgOrNull: String): String = if (msgOrNull == null) "" else msgOrNull - private def createReportMessage(diagnostics: Seq[Diagnostic], line: Int, column: Int): Seq[SnippetCompilerMessage] = { + private def createReportMessage(wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg, diagnostics: Seq[Diagnostic]): Seq[SnippetCompilerMessage] = { + val line = wrappedSnippet.lineOffset + val column = wrappedSnippet.columnOffset + val lineBoilerplate = wrappedSnippet.lineBoilerplate val infos = diagnostics.toSeq.sortBy(_.pos.source.path) val errorMessages = infos.map { case diagnostic if diagnostic.position.isPresent => val diagPos = diagnostic.position.get val pos = Some( - Position(diagPos.line + line, diagPos.column + column, diagPos.lineContent, diagPos.line) + Position(diagPos.line + line, diagPos.column + column, diagPos.lineContent, if arg.debug then diagPos.line else diagPos.line - lineBoilerplate) ) val msg = nullableMessage(diagnostic.message) val level = MessageLevel.fromOrdinal(diagnostic.level) @@ -85,7 +88,7 @@ class SnippetCompiler( run.compileFromStrings(List(wrappedSnippet.snippet)) val messages = - createReportMessage(context.reporter.pendingMessages(using context), wrappedSnippet.lineOffset, wrappedSnippet.columnOffset) ++ + createReportMessage(wrappedSnippet, arg, context.reporter.pendingMessages(using context)) ++ additionalMessages(wrappedSnippet, arg, context) val t = Option.when(!context.reporter.hasErrors)(target) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala index 7dad71a4ea02..dcba74b12533 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala @@ -11,7 +11,7 @@ sealed trait SCFlags(val flagName: String) object SCFlags: case object Compile extends SCFlags("compile") case object NoCompile extends SCFlags("nocompile") - case object Fail extends SCFlags("failing") + case object Fail extends SCFlags("fail") def values: Seq[SCFlags] = Seq(Compile, NoCompile, Fail) @@ -42,7 +42,7 @@ object SnippetCompilerArgs: |Available flags: |compile - Enables snippet checking. |nocompile - Disables snippet checking. - |failing - Enables snippet checking, asserts that snippet doesn't compile. + |fail - Enables snippet checking, asserts that snippet doesn't compile. | """.stripMargin diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala index 91229cb148a3..16f0dbc8f9b5 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala @@ -4,11 +4,11 @@ package snippets import java.io.ByteArrayOutputStream import java.io.PrintStream -case class WrappedSnippet(snippet: String, lineOffset: Int, columnOffset: Int) +case class WrappedSnippet(snippet: String, lineOffset: Int, columnOffset: Int, lineBoilerplate: Int, columnBoilerplate: Int) object WrappedSnippet: - private val lineOffset = 2 - private val columnOffset = 2 + private val lineBoilerplate = 2 + private val columnBoilerplate = 2 def apply(str: String): WrappedSnippet = val baos = new ByteArrayOutputStream() @@ -17,7 +17,7 @@ object WrappedSnippet: ps.println("object Snippet {") str.split('\n').foreach(ps.printlnWithIndent(2, _)) ps.println("}") - WrappedSnippet(baos.toString, lineOffset, columnOffset) + WrappedSnippet(baos.toString, 0, 0, lineBoilerplate, columnBoilerplate) def apply( str: String, @@ -35,7 +35,7 @@ object WrappedSnippet: ps.println(s"trait Snippet${classGenerics.getOrElse("")} { ${className.fold("")(cn => s"self: $cn =>")}") str.split('\n').foreach(ps.printlnWithIndent(2, _)) ps.println("}") - WrappedSnippet(baos.toString, lineOffset, columnOffset) + WrappedSnippet(baos.toString, lineOffset, columnOffset, lineBoilerplate, columnBoilerplate) extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) = ps.println((" " * indent) + str) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ScalaDocSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ScalaDocSupport.scala index a48101b00e2d..1c634df8c918 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ScalaDocSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ScalaDocSupport.scala @@ -29,9 +29,9 @@ object ScaladocSupport: val parser = commentSyntax match { case CommentSyntax.Wiki => - comments.WikiCommentParser(comments.Repr(quotes)(sym), snippetChecker) + comments.WikiCommentParser(comments.Repr(quotes)(sym)) case CommentSyntax.Markdown => - comments.MarkdownCommentParser(comments.Repr(quotes)(sym), snippetChecker) + comments.MarkdownCommentParser(comments.Repr(quotes)(sym)) } parser.parse(preparsed) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index 2b6dfed6bce9..eca976b39ceb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -71,7 +71,7 @@ case class PreparsedComment( case class DokkaCommentBody(summary: Option[DocPart], body: DocPart) -abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecker)(using dctx: DocContext) { +abstract class MarkupConversion[T](val repr: Repr)(using dctx: DocContext) { protected def stringToMarkup(str: String): T protected def markupToDokka(t: T): DocPart protected def markupToString(t: T): String @@ -80,6 +80,8 @@ abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecke protected def filterEmpty(xs: SortedMap[String, String]): SortedMap[String, T] protected def processSnippets(t: T): T + lazy val snippetChecker = dctx.snippetChecker + val qctx: repr.qctx.type = if repr == null then null else repr.qctx // TODO why we do need null? val owner: qctx.reflect.Symbol = if repr == null then null.asInstanceOf[qctx.reflect.Symbol] else repr.sym @@ -221,8 +223,8 @@ abstract class MarkupConversion[T](val repr: Repr, snippetChecker: SnippetChecke ) } -class MarkdownCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using dctx: DocContext) - extends MarkupConversion[mdu.Node](repr, snippetChecker) { +class MarkdownCommentParser(repr: Repr)(using dctx: DocContext) + extends MarkupConversion[mdu.Node](repr) { def stringToMarkup(str: String) = MarkdownParser.parseToMarkdown(str, markdown.DocFlexmarkParser(resolveLink)) @@ -248,56 +250,12 @@ class MarkdownCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using dc .filterNot { case (_, v) => v.isEmpty } .mapValues(stringToMarkup).to(SortedMap) - def processSnippets(root: mdu.Node): mdu.Node = { - val nodes = root.getDescendants().asScala.collect { - case fcb: mda.FencedCodeBlock => fcb - }.toList - if nodes.nonEmpty then { - val checkingFunc: SnippetChecker.SnippetCheckingFunc = snippetCheckingFunc(owner) - nodes.foreach { node => - val snippet = node.getContentChars.toString - val lineOffset = node.getStartLineNumber - val info = node.getInfo.toString - val argOverride = - info.split(" ") - .find(_.startsWith("sc:")) - .map(_.stripPrefix("sc:")) - .map(snippets.SCFlagsParser.parse) - .flatMap(_.toOption) - val snippetCompilationResult = checkingFunc(snippet, lineOffset, argOverride) match { - case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if dctx.snippetCompilerArgs.debug => - val s = sequence.BasedSequence.EmptyBasedSequence() - .append(wrapped) - .append(sequence.BasedSequence.EOL) - val content = mdu.BlockContent() - content.add(s, 0) - node.setContent(content) - result - case result => - result.map { r => - r.copy( - messages = r.messages.map { m => - m.copy( - position = m.position.map { p => - p.copy( - relativeLine = p.relativeLine - lineOffset - ) - } - ) - } - ) - } - } - node.insertBefore(new ExtendedFencedCodeBlock(node, snippetCompilationResult)) - node.unlink() - } - } - root - } + def processSnippets(root: mdu.Node): mdu.Node = + FlexmarkSnippetProcessor.processSnippets(root, dctx.snippetCompilerArgs.debug, snippetCheckingFunc(owner)) } -class WikiCommentParser(repr: Repr, snippetChecker: SnippetChecker)(using DocContext) - extends MarkupConversion[wiki.Body](repr, snippetChecker): +class WikiCommentParser(repr: Repr)(using DocContext) + extends MarkupConversion[wiki.Body](repr): def stringToMarkup(str: String) = wiki.Parser(str, resolverLink).document() diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala index 7938ed571349..2868b901f191 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala @@ -57,15 +57,6 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String) extends HtmlRenderer.HtmlRendererExtension: def rendererOptions(opt: MutableDataHolder): Unit = () // noop - object ExtendedFencedCodeBlockHandler extends CustomNodeRenderer[ExtendedFencedCodeBlock]: - override def render(node: ExtendedFencedCodeBlock, c: NodeRendererContext, html: HtmlWriter): Unit = - html.raw( - SnippetRenderer.renderSnippetWithMessages( - node.codeBlock.getContentChars.toString.split("\n").map(_ + "\n").toSeq, - node.compilationResult.toSeq.flatMap(_.messages) - ) - ) - object Handler extends CustomNodeRenderer[DocLinkNode]: override def render(node: DocLinkNode, c: NodeRendererContext, html: HtmlWriter): Unit = html.raw(renderLink(node.target, node.body)) @@ -74,7 +65,6 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String) override def getNodeRenderingHandlers: JSet[NodeRenderingHandler[_]] = JSet( new NodeRenderingHandler(classOf[DocLinkNode], Handler), - new NodeRenderingHandler(classOf[ExtendedFencedCodeBlock], ExtendedFencedCodeBlockHandler), ) object Factory extends NodeRendererFactory: @@ -85,5 +75,10 @@ case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String) object DocFlexmarkRenderer: def render(node: Node)(renderLink: (DocLink, String) => String) = - val opts = MarkdownParser.mkMarkdownOptions(Seq(DocFlexmarkRenderer(renderLink))) + val opts = MarkdownParser.mkMarkdownOptions( + Seq( + DocFlexmarkRenderer(renderLink), + SnippetRenderingExtension + ) + ) HtmlRenderer.builder(opts).build().render(node) \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderingExtension.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderingExtension.scala new file mode 100644 index 000000000000..d798d21c4e9f --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderingExtension.scala @@ -0,0 +1,37 @@ +package dotty.tools.scaladoc +package tasty.comments.markdown + +import dotty.tools.scaladoc.snippets._ + +import com.vladsch.flexmark.html._ +import com.vladsch.flexmark.html.renderer._ +import com.vladsch.flexmark.parser._ +import com.vladsch.flexmark.ext.wikilink._ +import com.vladsch.flexmark.ext.wikilink.internal.WikiLinkLinkRefProcessor +import com.vladsch.flexmark.util.ast._ +import com.vladsch.flexmark.util.options._ +import com.vladsch.flexmark.util.sequence.BasedSequence +import com.vladsch.flexmark._ + +object SnippetRenderingExtension extends HtmlRenderer.HtmlRendererExtension: + def rendererOptions(opt: MutableDataHolder): Unit = () + object ExtendedFencedCodeBlockHandler extends CustomNodeRenderer[ExtendedFencedCodeBlock]: + override def render(node: ExtendedFencedCodeBlock, c: NodeRendererContext, html: HtmlWriter): Unit = + html.raw( + SnippetRenderer.renderSnippetWithMessages( + node.codeBlock.getContentChars.toString.split("\n").map(_ + "\n").toSeq, + node.compilationResult.toSeq.flatMap(_.messages) + ) + ) + + object Render extends NodeRenderer: + override def getNodeRenderingHandlers: JSet[NodeRenderingHandler[_]] = + JSet( + new NodeRenderingHandler(classOf[ExtendedFencedCodeBlock], ExtendedFencedCodeBlockHandler), + ) + + object Factory extends NodeRendererFactory: + override def create(options: DataHolder): NodeRenderer = Render + + def extend(htmlRendererBuilder: HtmlRenderer.Builder, tpe: String): Unit = + htmlRendererBuilder.nodeRendererFactory(Factory) \ No newline at end of file From 3e8995c92c0f2d1fd5dcd4fa5c6636ea5f121dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Wed, 14 Apr 2021 16:38:12 +0200 Subject: [PATCH 19/28] Further enhancements of snippet contexts --- project/Build.scala | 4 +- .../src/tests/snippetCompilerTest.scala | 76 ++++++++++++++++- scaladoc/src/dotty/tools/scaladoc/api.scala | 4 +- .../scaladoc/snippets/SelfTypePrinter.scala | 42 ++++++++++ .../scaladoc/snippets/SnippetChecker.scala | 3 +- .../SnippetCompilerDataCollector.scala | 84 +++++++++++++++++++ .../scaladoc/snippets/WrappedSnippet.scala | 16 ++-- .../scaladoc/tasty/comments/Comments.scala | 55 +----------- 8 files changed, 217 insertions(+), 67 deletions(-) create mode 100644 scaladoc/src/dotty/tools/scaladoc/snippets/SelfTypePrinter.scala create mode 100644 scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala diff --git a/project/Build.scala b/project/Build.scala index c0f25d09694c..6e8b949476ed 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1351,7 +1351,9 @@ object Build { "-siteroot", "docs", s"-source-links:docs=github://lampepfl/dotty/master#docs", "-doc-root-content", docRootFile.toString, - "-Ydocument-synthetic-types" + "-Ydocument-synthetic-types", + "-snippet-compiler:" + + s"$dottyLibRoot=compile" ) )) }.evaluated, diff --git a/scaladoc-testcases/src/tests/snippetCompilerTest.scala b/scaladoc-testcases/src/tests/snippetCompilerTest.scala index de77984d1a81..d388f09d83fa 100644 --- a/scaladoc-testcases/src/tests/snippetCompilerTest.scala +++ b/scaladoc-testcases/src/tests/snippetCompilerTest.scala @@ -42,11 +42,83 @@ package snippetCompiler * a() * ``` */ -class A { } +class A { + trait B + val a = new B { + /** + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + def a = 3 + } +} /** * ```scala sc:compile * val c: Int = 4.5 * ``` */ -class B { } \ No newline at end of file +class B { } + +trait Quotes { + val reflect: reflectModule = ??? + trait reflectModule { self: reflect.type => + /** + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + def a = 3 + } +} + +trait Quotes2[A] { + val r1: r1Module[_] = ??? + trait r1Module[A] { + type X + object Y { + /** + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + type YY + } + val z: zModule = ??? + trait zModule { + /** + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + type ZZ + } + } + object r2 { + type X + object Y { + /** + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + type YY + } + val z: zModule = ??? + trait zModule { + /** + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + type ZZ + } + } +} \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index 768a6f896fcc..91f0eb3cc5b4 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -236,11 +236,11 @@ case class TastyMemberSource(path: java.nio.file.Path, lineNumber: Int) object SnippetCompilerData: case class Position(line: Int, column: Int) + case class ClassInfo(tpe: Option[String], names: Seq[String], generics: Option[String]) case class SnippetCompilerData( packageName: String, - classType: Option[String], - classGenerics: Option[String], + classInfos: Seq[SnippetCompilerData.ClassInfo], imports: List[String], position: SnippetCompilerData.Position ) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SelfTypePrinter.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SelfTypePrinter.scala new file mode 100644 index 000000000000..1f51a29243a2 --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SelfTypePrinter.scala @@ -0,0 +1,42 @@ +package dotty.tools.scaladoc +package snippets + +import dotty.tools.dotc.printing.RefinedPrinter +import dotty.tools.dotc.core._ +import dotty.tools.dotc.printing.Texts._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Names._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.NameOps._ +import dotty.tools.dotc.core.TypeErasure.ErasedValueType +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Annotations.Annotation +import dotty.tools.dotc.core.Denotations._ +import dotty.tools.dotc.core.SymDenotations._ +import dotty.tools.dotc.core.StdNames.{nme, tpnme} +import dotty.tools.dotc.ast.{Trees, untpd} +import dotty.tools.dotc.typer.{Implicits, Namer, Applications} +import dotty.tools.dotc.typer.ProtoTypes._ +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.core.TypeApplications._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.util.Chars.isOperatorPart +import dotty.tools.dotc.transform.TypeUtils._ +import dotty.tools.dotc.transform.SymUtils._ + +import language.implicitConversions +import dotty.tools.dotc.util.{NameTransformer, SourcePosition} +import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} + +class SelfTypePrinter(using _ctx: Context) extends RefinedPrinter(_ctx): + + override def toTextSingleton(tp: SingletonType): Text = + tp match + case ConstantType(value) => + if value.tag == Constants.ByteTag || value.tag == Constants.ShortTag then + toText(value) ~ s" /*${value.tpe.show}*/" + else + toText(value) + case _: TermRef => toTextRef(tp) ~ ".type /*" ~ toTextGlobal(tp.underlying) ~ "*/" + case _ => "(" ~ toTextRef(tp) ~ ": " ~ toTextGlobal(tp.underlying) ~ ")" diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index 56901760ccec..3a0b22cd6645 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -25,8 +25,7 @@ class SnippetChecker(val classpath: String, val tastyDirs: Seq[File]): val wrapped = WrappedSnippet( snippet, data.map(_.packageName), - data.flatMap(_.classType), - data.flatMap(_.classGenerics), + data.fold(Nil)(_.classInfos), data.map(_.imports).getOrElse(Nil), lineOffset + data.fold(0)(_.position.line) + 1, data.fold(0)(_.position.column) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala new file mode 100644 index 000000000000..fb934d354b33 --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala @@ -0,0 +1,84 @@ +package dotty.tools.scaladoc +package snippets + +import scala.quoted._ +import dotty.tools.scaladoc.tasty.SymOps +import dotty.tools.dotc.core._ + +class SnippetCompilerDataCollector[Q <: Quotes](val qctx: Q): + import qctx.reflect._ + object SymOps extends SymOps[qctx.type](qctx) + export SymOps._ + + def getSnippetCompilerData(sym: Symbol): SnippetCompilerData = + val packageName = sym.packageName + if !sym.isPackageDef then sym.tree match { + case c: qctx.reflect.ClassDef => + import dotty.tools.dotc + import dotty.tools.dotc.core.Decorators._ + given dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + val printer: dotc.printing.Printer = SelfTypePrinter() + val classSym = c.symbol.asInstanceOf[Symbols.ClassSymbol] + + def getOwners(sym: Symbols.ClassSymbol): Seq[Symbols.ClassSymbol] = Seq(sym) ++ + (if sym.owner.isClass && !sym.owner.is(dotc.core.Flags.Package) then getOwners(sym.owner.asInstanceOf[Symbols.ClassSymbol]) else Nil) + + def collectNames(tp: Types.Type): Seq[String] = tp match { + case Types.AndType(t1, t2) => collectNames(t1) ++ collectNames(t2) + case Types.AppliedType(tpe, _) => collectNames(tpe) + case Types.AnnotatedType(tpe, _) => collectNames(tpe) + case t: Types.NamedType => if t.symbol.is(dotc.core.Flags.Module) then Seq() else Seq(t.symbol.name.show) + case t: Types.ThisType => Seq(t.cls.name.show) + case _ => Seq() + } + + val allSyms = getOwners(classSym) + + val allProcessed = allSyms.map { cSym => + def createTypeConstructor(tpe: Types.Type, topLevel: Boolean = true): String = tpe match { + case t @ Types.TypeBounds(upper, lower) => lower match { + case l: Types.HKTypeLambda => + (if topLevel then "" else "?") + l.paramInfos.map(p => createTypeConstructor(p, false)).mkString("[",", ","]") + case _ => (if topLevel then "" else "_") + } + } + val classType = + val ct = cSym.classInfo.selfType.toText(printer).show.replace(".this","").stripPrefix(s"$packageName.") + Some(ct) + val classNames = collectNames(cSym.classInfo.selfType) + val classGenerics = Option.when( + !cSym.typeParams.isEmpty + )( + cSym.typeParams.map(_.typeRef).map(t => + t.show + + createTypeConstructor(t.asInstanceOf[Types.TypeRef].underlying) + ).mkString("[",", ","]") + ) + SnippetCompilerData.ClassInfo(classType, classNames, classGenerics) + } + val firstProcessed = allProcessed.head + SnippetCompilerData(packageName, allProcessed.reverse, Nil, position(hackGetPositionOfDocstring(using qctx)(sym))) + case _ => getSnippetCompilerData(sym.maybeOwner) + } else SnippetCompilerData(packageName, Nil, Nil, position(hackGetPositionOfDocstring(using qctx)(sym))) + + private def position(p: Option[qctx.reflect.Position]): SnippetCompilerData.Position = + p.fold(SnippetCompilerData.Position(0, 0))(p => SnippetCompilerData.Position(p.startLine, p.startColumn)) + + private def hackGetPositionOfDocstring(using Quotes)(s: qctx.reflect.Symbol): Option[qctx.reflect.Position] = + import dotty.tools.dotc.core.Comments.CommentsContext + import dotty.tools.dotc + given ctx: Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + val docCtx = ctx.docCtx.getOrElse { + throw new RuntimeException( + "DocCtx could not be found and documentations are unavailable. This is a compiler-internal error." + ) + } + val span = docCtx.docstring(s.asInstanceOf[Symbols.Symbol]).span + s.pos.flatMap { pos => + docCtx.docstring(s.asInstanceOf[Symbols.Symbol]).map { docstring => + dotty.tools.dotc.util.SourcePosition( + pos.sourceFile.asInstanceOf[dotty.tools.dotc.util.SourceFile], + docstring.span + ).asInstanceOf[qctx.reflect.Position] + } + } \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala index 16f0dbc8f9b5..7ffe5d848201 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala @@ -22,8 +22,7 @@ object WrappedSnippet: def apply( str: String, packageName: Option[String], - className: Option[String], - classGenerics: Option[String], + classInfos: Seq[SnippetCompilerData.ClassInfo], imports: List[String], lineOffset: Int, columnOffset: Int @@ -32,10 +31,15 @@ object WrappedSnippet: val ps = new PrintStream(baos) ps.println(s"package ${packageName.getOrElse("snippets")}") imports.foreach(i => ps.println(s"import $i")) - ps.println(s"trait Snippet${classGenerics.getOrElse("")} { ${className.fold("")(cn => s"self: $cn =>")}") - str.split('\n').foreach(ps.printlnWithIndent(2, _)) - ps.println("}") - WrappedSnippet(baos.toString, lineOffset, columnOffset, lineBoilerplate, columnBoilerplate) + classInfos.zipWithIndex.foreach { (info, i) => + ps.printlnWithIndent(2 * i, s"trait Snippet$i${info.generics.getOrElse("")} { ${info.tpe.fold("")(cn => s"self: $cn =>")}") + info.names.foreach{ name => + ps.printlnWithIndent(2 * i + 2, s"val $name = self") + } + } + str.split('\n').foreach(ps.printlnWithIndent(classInfos.size * 2, _)) + (0 to classInfos.size -1).reverse.foreach( i => ps.printlnWithIndent(i * 2, "}")) + WrappedSnippet(baos.toString, lineOffset, columnOffset, classInfos.size + classInfos.flatMap(_.names).size, classInfos.size * 2 + 2) extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) = ps.println((" " * indent) + str) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index eca976b39ceb..b14b7a84854a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -125,64 +125,11 @@ abstract class MarkupConversion[T](val repr: Repr)(using dctx: DocContext) { case _ => None } - private def getSnippetCompilerData(sym: qctx.reflect.Symbol): SnippetCompilerData = - val packageName = sym.packageName - if !sym.isPackageDef then sym.tree match { - case c: qctx.reflect.ClassDef => - import qctx.reflect._ - import dotty.tools.dotc - given dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val cSym = c.symbol.asInstanceOf[dotc.core.Symbols.ClassSymbol] - - def createTypeConstructor(tpe: dotc.core.Types.Type, topLevel: Boolean = true): String = tpe match { - case t @ dotc.core.Types.TypeBounds(upper, lower) => lower match { - case l: dotc.core.Types.HKTypeLambda => - (if topLevel then "" else "?") + l.paramInfos.map(p => createTypeConstructor(p, false)).mkString("[",", ","]") - case _ => (if topLevel then "" else "_") - } - } - val classType = - val ct = cSym.classInfo.selfType.show.replace(".this.",".") - Some(ct) - val classGenerics = Option.when( - !cSym.typeParams.isEmpty - )( - cSym.typeParams.map(_.typeRef).map(t => - t.show + - createTypeConstructor(t.asInstanceOf[dotc.core.Types.TypeRef].underlying) - ).mkString("[",", ","]") - ) - SnippetCompilerData(packageName, classType, classGenerics, Nil, position(hackGetPositionOfDocstring(using qctx)(sym))) - case _ => getSnippetCompilerData(sym.maybeOwner) - } else SnippetCompilerData(packageName, None, None, Nil, position(hackGetPositionOfDocstring(using qctx)(sym))) - - private def position(p: Option[qctx.reflect.Position]): SnippetCompilerData.Position = - p.fold(SnippetCompilerData.Position(0, 0))(p => SnippetCompilerData.Position(p.startLine, p.startColumn)) - - private def hackGetPositionOfDocstring(using Quotes)(s: qctx.reflect.Symbol): Option[qctx.reflect.Position] = - import dotty.tools.dotc.core.Comments.CommentsContext - import dotty.tools.dotc - given ctx: dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val docCtx = ctx.docCtx.getOrElse { - throw new RuntimeException( - "DocCtx could not be found and documentations are unavailable. This is a compiler-internal error." - ) - } - val span = docCtx.docstring(s.asInstanceOf[dotc.core.Symbols.Symbol]).span - s.pos.flatMap { pos => - docCtx.docstring(s.asInstanceOf[dotc.core.Symbols.Symbol]).map { docstring => - dotty.tools.dotc.util.SourcePosition( - pos.sourceFile.asInstanceOf[dotty.tools.dotc.util.SourceFile], - docstring.span - ).asInstanceOf[qctx.reflect.Position] - } - } - def snippetCheckingFunc: qctx.reflect.Symbol => SnippetChecker.SnippetCheckingFunc = (s: qctx.reflect.Symbol) => { val path = s.source.map(_.path) val pathBasedArg = dctx.snippetCompilerArgs.get(path) - val data = getSnippetCompilerData(s) + val data = SnippetCompilerDataCollector[qctx.type](qctx).getSnippetCompilerData(s) (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => { val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_)) From d27e377f9bd5a920a3bc52f82bcc8b358ef89aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Wed, 14 Apr 2021 17:11:36 +0200 Subject: [PATCH 20/28] Filter non-scala snippets. Report errors in parsing override flags --- .../dotty/tools/scaladoc/site/templates.scala | 2 +- .../snippets/FlexmarkSnippetProcessor.scala | 52 +++++++++++-------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index 96dab5efa6cb..3385214b62f1 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -93,7 +93,7 @@ case class TemplateFile( // Snippet compiler currently supports markdown only val parser: Parser = Parser.builder(defaultMarkdownOptions).build() val parsedMd = parser.parse(rendered) - val processed = FlexmarkSnippetProcessor.processSnippets(parsedMd, ssctx.snippetCompilerArgs.debug, snippetCheckingFunc) + val processed = FlexmarkSnippetProcessor.processSnippets(parsedMd, ssctx.snippetCompilerArgs.debug, snippetCheckingFunc)(using ssctx.outerCtx) HtmlRenderer.builder(defaultMarkdownOptions).build().render(processed) layoutTemplate match diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala index 3cbd040efea2..2ae1e46cbe8c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala @@ -10,7 +10,7 @@ import collection.JavaConverters._ import dotty.tools.scaladoc.tasty.comments.markdown.ExtendedFencedCodeBlock object FlexmarkSnippetProcessor: - def processSnippets(root: mdu.Node, debug: Boolean, checkingFunc: => SnippetChecker.SnippetCheckingFunc): mdu.Node = { + def processSnippets(root: mdu.Node, debug: Boolean, checkingFunc: => SnippetChecker.SnippetCheckingFunc)(using CompilerContext): mdu.Node = { lazy val cf: SnippetChecker.SnippetCheckingFunc = checkingFunc val nodes = root.getDescendants().asScala.collect { @@ -20,27 +20,37 @@ object FlexmarkSnippetProcessor: nodes.foreach { node => val snippet = node.getContentChars.toString val lineOffset = node.getStartLineNumber - val info = node.getInfo.toString - val argOverride = - info.split(" ") - .find(_.startsWith("sc:")) - .map(_.stripPrefix("sc:")) - .map(SCFlagsParser.parse) - .flatMap(_.toOption) - val snippetCompilationResult = cf(snippet, lineOffset, argOverride) match { - case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if debug => - val s = sequence.BasedSequence.EmptyBasedSequence() - .append(wrapped) - .append(sequence.BasedSequence.EOL) - val content = mdu.BlockContent() - content.add(s, 0) - node.setContent(content) - result - case result => result - } + val info = node.getInfo.toString.split(" ") + if info.contains("scala") then { + val argOverride = + info + .find(_.startsWith("sc:")) + .map(_.stripPrefix("sc:")) + .map(SCFlagsParser.parse) + .flatMap(_ match { + case Right(flags) => Some(flags) + case Left(error) => + report.warning( + s"""|Error occured during parsing flags in snippet: + |$error""".stripMargin + ) + None + }) + val snippetCompilationResult = cf(snippet, lineOffset, argOverride) match { + case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if debug => + val s = sequence.BasedSequence.EmptyBasedSequence() + .append(wrapped) + .append(sequence.BasedSequence.EOL) + val content = mdu.BlockContent() + content.add(s, 0) + node.setContent(content) + result + case result => result + } - node.insertBefore(new ExtendedFencedCodeBlock(node, snippetCompilationResult)) - node.unlink() + node.insertBefore(new ExtendedFencedCodeBlock(node, snippetCompilationResult)) + node.unlink() + } } root From bb07b46d576162f55c23ae9c568287ec303e3238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 19 Apr 2021 11:37:15 +0200 Subject: [PATCH 21/28] Fix positions in static sites. Enchance snippet wrapping --- docs/css/dottydoc.css | 12 ------------ project/Build.scala | 3 ++- scaladoc-testcases/docs/docs/index.md | 4 ++++ .../resources/dotty_res/styles/scalastyle.css | 1 + .../src/dotty/tools/scaladoc/site/common.scala | 6 ++++-- .../src/dotty/tools/scaladoc/site/templates.scala | 9 ++++++++- .../snippets/SnippetCompilerDataCollector.scala | 2 +- .../tools/scaladoc/snippets/WrappedSnippet.scala | 15 +++++++++++---- 8 files changed, 31 insertions(+), 21 deletions(-) diff --git a/docs/css/dottydoc.css b/docs/css/dottydoc.css index caeb967d6fd1..d2520888120a 100644 --- a/docs/css/dottydoc.css +++ b/docs/css/dottydoc.css @@ -180,18 +180,6 @@ h5:hover a.anchor:hover { font-family: var(--font-family-monospace); } -/* code */ -pre, code { - font-variant-ligatures: none; -} -pre { - padding: 0; - font-size: 13px; - background: var(--pre-bg); - border-radius: 2px; - border: 1px solid rgba(0, 0, 0, 0.1); -} - /* admonitions */ blockquote { padding: 0 1em; diff --git a/project/Build.scala b/project/Build.scala index 6e8b949476ed..4209dbabda56 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1353,7 +1353,8 @@ object Build { "-doc-root-content", docRootFile.toString, "-Ydocument-synthetic-types", "-snippet-compiler:" + - s"$dottyLibRoot=compile" + s"$dottyLibRoot=compile" + + "docs=compile" ) )) }.evaluated, diff --git a/scaladoc-testcases/docs/docs/index.md b/scaladoc-testcases/docs/docs/index.md index f0465b1067c2..69036cd254a4 100644 --- a/scaladoc-testcases/docs/docs/index.md +++ b/scaladoc-testcases/docs/docs/index.md @@ -7,3 +7,7 @@ 2 + List(0) ``` +```scala sc:compile +new snippetCompiler.Snippet0 { } +``` + diff --git a/scaladoc/resources/dotty_res/styles/scalastyle.css b/scaladoc/resources/dotty_res/styles/scalastyle.css index 610b7b6b1d93..c32f351bb82d 100644 --- a/scaladoc/resources/dotty_res/styles/scalastyle.css +++ b/scaladoc/resources/dotty_res/styles/scalastyle.css @@ -93,6 +93,7 @@ code { padding: 0 .3em; } pre { + overflow: visible; scrollbar-width: thin; margin: 0px; } diff --git a/scaladoc/src/dotty/tools/scaladoc/site/common.scala b/scaladoc/src/dotty/tools/scaladoc/site/common.scala index 733ffa994afc..49c4621a90dc 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/common.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/common.scala @@ -49,7 +49,8 @@ def emptyTemplate(file: File, title: String): TemplateFile = TemplateFile( title = title, hasFrame = true, resources = List.empty, - layout = None + layout = None, + configOffset = 0 ) final val ConfigSeparator = "---" @@ -107,6 +108,7 @@ def loadTemplateFile(file: File): TemplateFile = { title = stringSetting(allSettings, "title").getOrElse(name), hasFrame = !stringSetting(allSettings, "hasFrame").contains("false"), resources = (listSetting(allSettings, "extraCSS") ++ listSetting(allSettings, "extraJS")).flatten.toList, - layout = stringSetting(allSettings, "layout") + layout = stringSetting(allSettings, "layout"), + configOffset = config.size ) } diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index 3385214b62f1..d832659ab4bd 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -53,6 +53,7 @@ case class TemplateFile( hasFrame: Boolean, resources: List[String], layout: Option[String], + configOffset: Int ): def isIndexPage() = file.isFile && (file.getName == "index.md" || file.getName == "index.html") @@ -63,7 +64,13 @@ case class TemplateFile( val pathBasedArg = ssctx.snippetCompilerArgs.get(path) (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => { val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_)) - ssctx.snippetChecker.checkSnippet(str, None, arg, lineOffset).collect { + val compilerData = SnippetCompilerData( + "staticsitesnippet", + Seq(SnippetCompilerData.ClassInfo(None, Nil, None)), + Nil, + SnippetCompilerData.Position(configOffset - 1, 0) + ) + ssctx.snippetChecker.checkSnippet(str, Some(compilerData), arg, lineOffset).collect { case r: SnippetCompilationResult if !r.isSuccessful => val msg = s"In static site (${file.getAbsolutePath}):\n${r.getSummary}" report.error(msg)(using ssctx.outerCtx) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala index fb934d354b33..ee70cb4d1e7c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala @@ -43,7 +43,7 @@ class SnippetCompilerDataCollector[Q <: Quotes](val qctx: Q): } } val classType = - val ct = cSym.classInfo.selfType.toText(printer).show.replace(".this","").stripPrefix(s"$packageName.") + val ct = cSym.classInfo.selfType.toText(printer).show.replace(".this","").replace("\n", " ").stripPrefix(s"$packageName.") Some(ct) val classNames = collectNames(cSym.classInfo.selfType) val classGenerics = Option.when( diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala index 7ffe5d848201..c13ddfb9d7f2 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala @@ -31,15 +31,22 @@ object WrappedSnippet: val ps = new PrintStream(baos) ps.println(s"package ${packageName.getOrElse("snippets")}") imports.foreach(i => ps.println(s"import $i")) - classInfos.zipWithIndex.foreach { (info, i) => + val notEmptyClassInfos = if classInfos.isEmpty then Seq(SnippetCompilerData.ClassInfo(None, Nil, None)) else classInfos + notEmptyClassInfos.zipWithIndex.foreach { (info, i) => ps.printlnWithIndent(2 * i, s"trait Snippet$i${info.generics.getOrElse("")} { ${info.tpe.fold("")(cn => s"self: $cn =>")}") info.names.foreach{ name => ps.printlnWithIndent(2 * i + 2, s"val $name = self") } } - str.split('\n').foreach(ps.printlnWithIndent(classInfos.size * 2, _)) - (0 to classInfos.size -1).reverse.foreach( i => ps.printlnWithIndent(i * 2, "}")) - WrappedSnippet(baos.toString, lineOffset, columnOffset, classInfos.size + classInfos.flatMap(_.names).size, classInfos.size * 2 + 2) + str.split('\n').foreach(ps.printlnWithIndent(notEmptyClassInfos.size * 2, _)) + (0 to notEmptyClassInfos.size -1).reverse.foreach( i => ps.printlnWithIndent(i * 2, "}")) + WrappedSnippet( + baos.toString, + lineOffset, + columnOffset, + notEmptyClassInfos.size + notEmptyClassInfos.flatMap(_.names).size + packageName.size, + notEmptyClassInfos.size * 2 + 2 + ) extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) = ps.println((" " * indent) + str) From 30eec6951e4b7fa579f6307b6c3b5a8afb379763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Tue, 20 Apr 2021 09:41:19 +0200 Subject: [PATCH 22/28] CRs --- project/Build.scala | 2 +- .../tools/scaladoc/snippets/SnippetCompiler.scala | 8 ++++---- .../tools/scaladoc/snippets/WrappedSnippet.scala | 14 ++++++-------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 4209dbabda56..4b8d78bdf357 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1353,7 +1353,7 @@ object Build { "-doc-root-content", docRootFile.toString, "-Ydocument-synthetic-types", "-snippet-compiler:" + - s"$dottyLibRoot=compile" + + s"$dottyLibRoot=compile," + "docs=compile" ) )) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index 34d2c8dd982e..ce69f48382d2 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -43,15 +43,15 @@ class SnippetCompiler( if (msgOrNull == null) "" else msgOrNull private def createReportMessage(wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg, diagnostics: Seq[Diagnostic]): Seq[SnippetCompilerMessage] = { - val line = wrappedSnippet.lineOffset - val column = wrappedSnippet.columnOffset - val lineBoilerplate = wrappedSnippet.lineBoilerplate + val line = wrappedSnippet.outerLineOffset + val column = wrappedSnippet.outerColumnOffset + val innerLineOffset = wrappedSnippet.innerLineOffset val infos = diagnostics.toSeq.sortBy(_.pos.source.path) val errorMessages = infos.map { case diagnostic if diagnostic.position.isPresent => val diagPos = diagnostic.position.get val pos = Some( - Position(diagPos.line + line, diagPos.column + column, diagPos.lineContent, if arg.debug then diagPos.line else diagPos.line - lineBoilerplate) + Position(diagPos.line + line, diagPos.column + column, diagPos.lineContent, if arg.debug then diagPos.line else diagPos.line - innerLineOffset) ) val msg = nullableMessage(diagnostic.message) val level = MessageLevel.fromOrdinal(diagnostic.level) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala index c13ddfb9d7f2..de1ed2e08347 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala @@ -4,11 +4,9 @@ package snippets import java.io.ByteArrayOutputStream import java.io.PrintStream -case class WrappedSnippet(snippet: String, lineOffset: Int, columnOffset: Int, lineBoilerplate: Int, columnBoilerplate: Int) +case class WrappedSnippet(snippet: String, outerLineOffset: Int, outerColumnOffset: Int, innerLineOffset: Int, innerColumnOffset: Int) object WrappedSnippet: - private val lineBoilerplate = 2 - private val columnBoilerplate = 2 def apply(str: String): WrappedSnippet = val baos = new ByteArrayOutputStream() @@ -17,15 +15,15 @@ object WrappedSnippet: ps.println("object Snippet {") str.split('\n').foreach(ps.printlnWithIndent(2, _)) ps.println("}") - WrappedSnippet(baos.toString, 0, 0, lineBoilerplate, columnBoilerplate) + WrappedSnippet(baos.toString, 0, 0, 2, 2) def apply( str: String, packageName: Option[String], classInfos: Seq[SnippetCompilerData.ClassInfo], imports: List[String], - lineOffset: Int, - columnOffset: Int + outerLineOffset: Int, + outerColumnOffset: Int ): WrappedSnippet = val baos = new ByteArrayOutputStream() val ps = new PrintStream(baos) @@ -42,8 +40,8 @@ object WrappedSnippet: (0 to notEmptyClassInfos.size -1).reverse.foreach( i => ps.printlnWithIndent(i * 2, "}")) WrappedSnippet( baos.toString, - lineOffset, - columnOffset, + outerLineOffset, + outerColumnOffset, notEmptyClassInfos.size + notEmptyClassInfos.flatMap(_.names).size + packageName.size, notEmptyClassInfos.size * 2 + 2 ) From d0bf3914aa3212e822816bd4fb6a7a2fc3b13d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Thu, 22 Apr 2021 16:56:57 +0200 Subject: [PATCH 23/28] Add and update tests for snippet compiler. Bugfixes found during testing --- ...rTest.scala => snippetCompilerTests.scala} | 60 +------ .../src/tests/snippetTestcase1.scala | 12 ++ .../src/tests/snippetTestcase2.scala | 58 ++++++ .../snippets/FlexmarkSnippetProcessor.scala | 2 +- .../scaladoc/snippets/SnippetChecker.scala | 16 +- .../snippets/SnippetCompilationResult.scala | 2 +- .../scaladoc/snippets/SnippetCompiler.scala | 7 +- .../SnippetCompilerDataCollector.scala | 9 +- .../scaladoc/snippets/WrappedSnippet.scala | 16 +- .../scaladoc/tasty/comments/Comments.scala | 2 +- .../dotty/tools/scaladoc/ScaladocTest.scala | 2 +- .../scaladoc/site/TemplateFileTests.scala | 1 + .../snippets/SnippetCompilerTest.scala | 29 ++- .../scaladoc/snippets/SnippetsE2eTest.scala | 170 ++++++++++++++++++ .../snippets/SnippetsE2eTestcases.scala | 10 ++ .../test/dotty/tools/scaladoc/testUtils.scala | 3 +- 16 files changed, 300 insertions(+), 99 deletions(-) rename scaladoc-testcases/src/tests/{snippetCompilerTest.scala => snippetCompilerTests.scala} (50%) create mode 100644 scaladoc-testcases/src/tests/snippetTestcase1.scala create mode 100644 scaladoc-testcases/src/tests/snippetTestcase2.scala create mode 100644 scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala create mode 100644 scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTestcases.scala diff --git a/scaladoc-testcases/src/tests/snippetCompilerTest.scala b/scaladoc-testcases/src/tests/snippetCompilerTests.scala similarity index 50% rename from scaladoc-testcases/src/tests/snippetCompilerTest.scala rename to scaladoc-testcases/src/tests/snippetCompilerTests.scala index d388f09d83fa..85d4db4ee0a2 100644 --- a/scaladoc-testcases/src/tests/snippetCompilerTest.scala +++ b/scaladoc-testcases/src/tests/snippetCompilerTests.scala @@ -1,4 +1,5 @@ -package snippetCompiler +package tests +package snippetCompilerTests /** * ```scala sc:compile @@ -44,15 +45,6 @@ package snippetCompiler */ class A { trait B - val a = new B { - /** - * ```scala sc:compile - * 2 + List() - * ``` - * - */ - def a = 3 - } } /** @@ -73,52 +65,4 @@ trait Quotes { */ def a = 3 } -} - -trait Quotes2[A] { - val r1: r1Module[_] = ??? - trait r1Module[A] { - type X - object Y { - /** - * ```scala sc:compile - * 2 + List() - * ``` - * - */ - type YY - } - val z: zModule = ??? - trait zModule { - /** - * ```scala sc:compile - * 2 + List() - * ``` - * - */ - type ZZ - } - } - object r2 { - type X - object Y { - /** - * ```scala sc:compile - * 2 + List() - * ``` - * - */ - type YY - } - val z: zModule = ??? - trait zModule { - /** - * ```scala sc:compile - * 2 + List() - * ``` - * - */ - type ZZ - } - } } \ No newline at end of file diff --git a/scaladoc-testcases/src/tests/snippetTestcase1.scala b/scaladoc-testcases/src/tests/snippetTestcase1.scala new file mode 100644 index 000000000000..b0907f983946 --- /dev/null +++ b/scaladoc-testcases/src/tests/snippetTestcase1.scala @@ -0,0 +1,12 @@ +package tests.snippetTestcase1 + +class SnippetTestcase1: + /** + * SNIPPET(OUTERLINEOFFSET:8,OUTERCOLUMNOFFSET:6,INNERLINEOFFSET:3,INNERCOLUMNOFFSET:2) + * ERROR(LINE:8,COLUMN:8) + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + def a = 3 \ No newline at end of file diff --git a/scaladoc-testcases/src/tests/snippetTestcase2.scala b/scaladoc-testcases/src/tests/snippetTestcase2.scala new file mode 100644 index 000000000000..8e3fd0f33744 --- /dev/null +++ b/scaladoc-testcases/src/tests/snippetTestcase2.scala @@ -0,0 +1,58 @@ +package tests +package snippetTestcase2 + +trait Quotes2[A] { + val r1: r1Module[_] = ??? + trait r1Module[A] { + type X + object Y { + /** + * SNIPPET(OUTERLINEOFFSET:13,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:6,INNERCOLUMNOFFSET:6) + * ERROR(LINE:13,COLUMN:12) + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + type YY + } + val z: zModule = ??? + trait zModule { + /** + * SNIPPET(OUTERLINEOFFSET:25,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:7,INNERCOLUMNOFFSET:6) + * ERROR(LINE:25,COLUMN:12) + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + type ZZ + } + } + object r2 { + type X + object Y { + /** + * SNIPPET(OUTERLINEOFFSET:39,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:5,INNERCOLUMNOFFSET:6) + * ERROR(LINE:39,COLUMN:12) + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + type YY + } + val z: zModule = ??? + trait zModule { + /** + * SNIPPET(OUTERLINEOFFSET:51,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:6,INNERCOLUMNOFFSET:6) + * ERROR(LINE:51,COLUMN:12) + * ```scala sc:compile + * 2 + List() + * ``` + * + */ + type ZZ + } + } +} \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala index 2ae1e46cbe8c..eb67e2e1fc2a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala @@ -39,7 +39,7 @@ object FlexmarkSnippetProcessor: val snippetCompilationResult = cf(snippet, lineOffset, argOverride) match { case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if debug => val s = sequence.BasedSequence.EmptyBasedSequence() - .append(wrapped) + .append(wrapped.snippet) .append(sequence.BasedSequence.EOL) val content = mdu.BlockContent() content.add(s, 0) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index 3a0b22cd6645..f61057c39233 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -8,13 +8,19 @@ import java.io.File class SnippetChecker(val classpath: String, val tastyDirs: Seq[File]): private val sep = System.getProperty("path.separator") private val cp = List( - System.getProperty("java.class.path"), - Paths.get(classpath).toAbsolutePath, - tastyDirs.map(_.getAbsolutePath()).mkString(sep) + tastyDirs.map(_.getAbsolutePath()).mkString(sep), + classpath, + System.getProperty("java.class.path") ).mkString(sep) + private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp) + // These constants were found empirically to make snippet compiler + // report errors in the same position as main compiler. + private val constantLineOffset = 3 + private val constantColumnOffset = 4 + def checkSnippet( snippet: String, data: Option[SnippetCompilerData], @@ -27,8 +33,8 @@ class SnippetChecker(val classpath: String, val tastyDirs: Seq[File]): data.map(_.packageName), data.fold(Nil)(_.classInfos), data.map(_.imports).getOrElse(Nil), - lineOffset + data.fold(0)(_.position.line) + 1, - data.fold(0)(_.position.column) + lineOffset + data.fold(0)(_.position.line) + constantLineOffset, + data.fold(0)(_.position.column) + constantColumnOffset ) val res = compiler.compile(wrapped, arg) Some(res) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala index d44a7fb23b8f..f4fe2f8223fb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala @@ -12,7 +12,7 @@ case class SnippetCompilerMessage(position: Option[Position], message: String, l } case class SnippetCompilationResult( - wrappedSnippet: String, + wrappedSnippet: WrappedSnippet, isSuccessful: Boolean, result: Option[AbstractFile], messages: Seq[SnippetCompilerMessage] diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index ce69f48382d2..d50d64fc19bc 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -19,7 +19,7 @@ import dotty.tools.dotc.util.SourceFile import dotty.tools.dotc.interfaces.Diagnostic._ class SnippetCompiler( - classpath: String = System.getProperty("java.class.path"), //Probably needs to be done better + classpath: String, val scalacOptions: String = "", target: AbstractFile = new VirtualDirectory("(memory)") ): @@ -46,12 +46,13 @@ class SnippetCompiler( val line = wrappedSnippet.outerLineOffset val column = wrappedSnippet.outerColumnOffset val innerLineOffset = wrappedSnippet.innerLineOffset + val innerColumnOffset = wrappedSnippet.innerColumnOffset val infos = diagnostics.toSeq.sortBy(_.pos.source.path) val errorMessages = infos.map { case diagnostic if diagnostic.position.isPresent => val diagPos = diagnostic.position.get val pos = Some( - Position(diagPos.line + line, diagPos.column + column, diagPos.lineContent, if arg.debug then diagPos.line else diagPos.line - innerLineOffset) + Position(diagPos.line + line - innerLineOffset, diagPos.column + column - innerColumnOffset, diagPos.lineContent, if arg.debug then diagPos.line else diagPos.line - innerLineOffset) ) val msg = nullableMessage(diagnostic.message) val level = MessageLevel.fromOrdinal(diagnostic.level) @@ -92,5 +93,5 @@ class SnippetCompiler( additionalMessages(wrappedSnippet, arg, context) val t = Option.when(!context.reporter.hasErrors)(target) - SnippetCompilationResult(wrappedSnippet.snippet, isSuccessful(arg, context), t, messages) + SnippetCompilationResult(wrappedSnippet, isSuccessful(arg, context), t, messages) } diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala index ee70cb4d1e7c..ae52b5e4c204 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala @@ -10,7 +10,7 @@ class SnippetCompilerDataCollector[Q <: Quotes](val qctx: Q): object SymOps extends SymOps[qctx.type](qctx) export SymOps._ - def getSnippetCompilerData(sym: Symbol): SnippetCompilerData = + def getSnippetCompilerData(sym: Symbol, originalSym: Symbol): SnippetCompilerData = val packageName = sym.packageName if !sym.isPackageDef then sym.tree match { case c: qctx.reflect.ClassDef => @@ -57,9 +57,9 @@ class SnippetCompilerDataCollector[Q <: Quotes](val qctx: Q): SnippetCompilerData.ClassInfo(classType, classNames, classGenerics) } val firstProcessed = allProcessed.head - SnippetCompilerData(packageName, allProcessed.reverse, Nil, position(hackGetPositionOfDocstring(using qctx)(sym))) - case _ => getSnippetCompilerData(sym.maybeOwner) - } else SnippetCompilerData(packageName, Nil, Nil, position(hackGetPositionOfDocstring(using qctx)(sym))) + SnippetCompilerData(packageName, allProcessed.reverse, Nil, position(hackGetPositionOfDocstring(using qctx)(originalSym))) + case _ => getSnippetCompilerData(sym.maybeOwner, originalSym) + } else SnippetCompilerData(packageName, Nil, Nil, position(hackGetPositionOfDocstring(using qctx)(originalSym))) private def position(p: Option[qctx.reflect.Position]): SnippetCompilerData.Position = p.fold(SnippetCompilerData.Position(0, 0))(p => SnippetCompilerData.Position(p.startLine, p.startColumn)) @@ -73,7 +73,6 @@ class SnippetCompilerDataCollector[Q <: Quotes](val qctx: Q): "DocCtx could not be found and documentations are unavailable. This is a compiler-internal error." ) } - val span = docCtx.docstring(s.asInstanceOf[Symbols.Symbol]).span s.pos.flatMap { pos => docCtx.docstring(s.asInstanceOf[Symbols.Symbol]).map { docstring => dotty.tools.dotc.util.SourcePosition( diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala index de1ed2e08347..e12078217c7e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala @@ -8,14 +8,16 @@ case class WrappedSnippet(snippet: String, outerLineOffset: Int, outerColumnOffs object WrappedSnippet: + val indent: Int = 2 + def apply(str: String): WrappedSnippet = val baos = new ByteArrayOutputStream() val ps = new PrintStream(baos) ps.println("package snippets") ps.println("object Snippet {") - str.split('\n').foreach(ps.printlnWithIndent(2, _)) + str.split('\n').foreach(ps.printlnWithIndent(indent, _)) ps.println("}") - WrappedSnippet(baos.toString, 0, 0, 2, 2) + WrappedSnippet(baos.toString, 0, 0, indent, indent) def apply( str: String, @@ -31,19 +33,19 @@ object WrappedSnippet: imports.foreach(i => ps.println(s"import $i")) val notEmptyClassInfos = if classInfos.isEmpty then Seq(SnippetCompilerData.ClassInfo(None, Nil, None)) else classInfos notEmptyClassInfos.zipWithIndex.foreach { (info, i) => - ps.printlnWithIndent(2 * i, s"trait Snippet$i${info.generics.getOrElse("")} { ${info.tpe.fold("")(cn => s"self: $cn =>")}") + ps.printlnWithIndent(indent * i, s"trait Snippet$i${info.generics.getOrElse("")} { ${info.tpe.fold("")(cn => s"self: $cn =>")}") info.names.foreach{ name => - ps.printlnWithIndent(2 * i + 2, s"val $name = self") + ps.printlnWithIndent(indent * i + indent, s"val $name = self") } } - str.split('\n').foreach(ps.printlnWithIndent(notEmptyClassInfos.size * 2, _)) - (0 to notEmptyClassInfos.size -1).reverse.foreach( i => ps.printlnWithIndent(i * 2, "}")) + str.split('\n').foreach(ps.printlnWithIndent(notEmptyClassInfos.size * indent, _)) + (0 to notEmptyClassInfos.size -1).reverse.foreach( i => ps.printlnWithIndent(i * indent, "}")) WrappedSnippet( baos.toString, outerLineOffset, outerColumnOffset, notEmptyClassInfos.size + notEmptyClassInfos.flatMap(_.names).size + packageName.size, - notEmptyClassInfos.size * 2 + 2 + notEmptyClassInfos.size * indent ) extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) = diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index b14b7a84854a..ef08dbbf3f7f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -129,7 +129,7 @@ abstract class MarkupConversion[T](val repr: Repr)(using dctx: DocContext) { (s: qctx.reflect.Symbol) => { val path = s.source.map(_.path) val pathBasedArg = dctx.snippetCompilerArgs.get(path) - val data = SnippetCompilerDataCollector[qctx.type](qctx).getSnippetCompilerData(s) + val data = SnippetCompilerDataCollector[qctx.type](qctx).getSnippetCompilerData(s, s) (str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => { val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_)) diff --git a/scaladoc/test/dotty/tools/scaladoc/ScaladocTest.scala b/scaladoc/test/dotty/tools/scaladoc/ScaladocTest.scala index 1ca6dcd04367..ad93d773cf4f 100644 --- a/scaladoc/test/dotty/tools/scaladoc/ScaladocTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/ScaladocTest.scala @@ -16,7 +16,7 @@ abstract class ScaladocTest(val name: String): given DocContext = testDocContext(tastyFiles(name)) op(ScalaModuleProvider.mkModule()) - private def getTempDir() : TemporaryFolder = + protected def getTempDir() : TemporaryFolder = val folder = new TemporaryFolder() folder.create() folder diff --git a/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala b/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala index d7ff5340f46f..06d0fd59d055 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala @@ -8,6 +8,7 @@ import org.junit.Test import java.nio.file.Files class TemplateFileTests: + given staticSiteContext: StaticSiteContext = testDocContext().staticSiteContext.get private def testTemplate(code: String, ext: String = "html")(op: TemplateFile => Unit): Unit = val tmpFile = Files.createTempFile("headerTests", s".${ext}").toFile() try diff --git a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala index 19a3e54e465c..65aca6021b71 100644 --- a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala @@ -6,33 +6,34 @@ import org.junit.Assert._ import dotty.tools.io.{AbstractFile, VirtualDirectory} class SnippetCompilerTest { - val compiler = SnippetCompiler() - def runTest(str: String) = compiler.compile(str) + val compiler = SnippetCompiler(System.getProperty("java.class.path")) + def wrapFn: String => WrappedSnippet = (str: String) => WrappedSnippet( + str, + Some("test"), + Nil, + Nil, + 0, + 0 + ) - def runTest(str: List[String]) = compiler.compile(str) + def runTest(str: String) = compiler.compile(wrapFn(str), SnippetCompilerArg(SCFlags.Compile, false)) private def assertSuccessfulCompilation(res: SnippetCompilationResult): Unit = res match { - case SnippetCompilationResult(Some(target), _) => assert(true) - case r @ SnippetCompilationResult(None, _) => assert(false, r.getSummary) + case r @ SnippetCompilationResult(_, isSuccessful, _, messages) => assert(isSuccessful, r.getSummary) } private def assertFailedCompilation(res: SnippetCompilationResult): Unit = res match { - case SnippetCompilationResult(Some(target), _) => assert(false, "Expected compilation failure") - case r @ SnippetCompilationResult(None, _) => assert(true) + case r @ SnippetCompilationResult(_, isSuccessful, _, messages) => assert(!isSuccessful, r.getSummary) } def assertSuccessfulCompilation(str: String): Unit = assertSuccessfulCompilation(runTest(str)) def assertFailedCompilation(str: String): Unit = assertFailedCompilation(runTest(str)) - def assertSuccessfulCompilation(str: List[String]): Unit = assertSuccessfulCompilation(runTest(str)) - - def assertFailedCompilation(str: List[String]): Unit = assertFailedCompilation(runTest(str)) - def assertMessageLevelPresent(str: String, level: MessageLevel): Unit = assertMessageLevelPresent(runTest(str), level) def assertMessageLevelPresent(res: SnippetCompilationResult, level: MessageLevel): Unit = res match { - case r @ SnippetCompilationResult(_, messages) => assertTrue( + case r @ SnippetCompilationResult(_, isSuccessful, _, messages) => assertTrue( s"Expected message with level: ${level.text}. Got result ${r.getSummary}", messages.exists(_.level == level) ) @@ -42,18 +43,15 @@ class SnippetCompilerTest { @Test def snippetCompilerTest: Unit = { val simpleCorrectSnippet = s""" - |package asd |class A: | val b: String = "asd" |""".stripMargin val simpleIncorrectSnippet = s""" - |package asd |class A: | val b: String |""".stripMargin val warningSnippet = s""" - |package asd |class A: | val a: Int = try { | 5 @@ -61,7 +59,6 @@ class SnippetCompilerTest { |""".stripMargin assertSuccessfulCompilation(simpleCorrectSnippet) assertFailedCompilation(simpleIncorrectSnippet) - assertFailedCompilation(List(simpleCorrectSnippet, simpleCorrectSnippet)) assertMessageLevelPresent(simpleIncorrectSnippet, MessageLevel.Error) assertMessageLevelPresent(warningSnippet, MessageLevel.Warning) //No test for Info diff --git a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala new file mode 100644 index 000000000000..f80b709f3ce9 --- /dev/null +++ b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala @@ -0,0 +1,170 @@ +package dotty.tools.scaladoc +package snippets + +import scala.io.Source + +import org.junit.Test +import org.junit.Assert._ +import dotty.tools.io.{AbstractFile, VirtualDirectory} +import dotty.tools.scaladoc.test.BuildInfo +import scala.util.matching.Regex +import dotty.tools.dotc.reporting.{ Diagnostic, StoreReporter } + +import com.vladsch.flexmark.util.{ast => mdu, sequence} +import com.vladsch.flexmark.{ast => mda} +import com.vladsch.flexmark.formatter.Formatter +import com.vladsch.flexmark.util.options.MutableDataSet +import collection.JavaConverters._ + +import dotty.tools.scaladoc.tasty.comments.markdown.ExtendedFencedCodeBlock + +abstract class SnippetsE2eTest(testName: String, flag: SCFlags, debug: Boolean) extends ScaladocTest(testName): + + import SnippetsE2eTest._ + + val source = Source.fromFile(s"${BuildInfo.test_testcasesSourceRoot}/tests/$testName.scala") + + val snippetsCount = source.getLines.filter(_.indexOf("```scala") != -1).size + + def report(str: String) = s"""|In test $testName: + |$str""".stripMargin + + println(BuildInfo.test_testcasesOutputDir.map(_ + s"/tests/$testName")) + + override def args = Scaladoc.Args( + name = "test", + tastyDirs = BuildInfo.test_testcasesOutputDir.map(java.io.File(_)).toSeq, + tastyFiles = tastyFiles(testName), + output = getTempDir().getRoot, + projectVersion = Some("1.0"), + snippetCompiler = List(s"${BuildInfo.test_testcasesSourceRoot}/tests=${flag.flagName}"), + snippetCompilerDebug = debug + ) + + override def withModule(op: DocContext ?=> Module => Unit) = + given DocContext = DocContext(args, testContext.fresh.setReporter(new StoreReporter)) + op(ScalaModuleProvider.mkModule()) + + private def checkWrappedSnippet(ws: WrappedSnippet, si: SnippetInfo) = { + assertTrue( + report( + s"Invalid outer line offset: ${ws.outerLineOffset}. " + + s"Expected: ${si.outerOffset.line}\n" + ), + ws.outerLineOffset == si.outerOffset.line + ) + assertTrue( + report( + s"Invalid outer column offset: ${ws.outerColumnOffset}. " + + s"Expected: ${si.outerOffset.column}\n" + ), + ws.outerColumnOffset == si.outerOffset.column + ) + assertTrue( + report( + s"Invalid inner line offset: ${ws.innerLineOffset}. " + + s"Expected: ${si.innerOffset.line}\n" + ), + ws.innerLineOffset == si.innerOffset.line + ) + assertTrue( + report( + s"Invalid inner column offset: ${ws.innerColumnOffset}. " + + s"Expected: ${si.innerOffset.column}\n" + ), + ws.innerColumnOffset == si.innerOffset.column + ) + } + + private def checkMessages(compilationMessages: Seq[SnippetCompilerMessage], messages: Seq[Message], ws: WrappedSnippet) = { + val compilationMessagesWithPos = compilationMessages.collect { + case m @ SnippetCompilerMessage(Some(_), _, _) => m + }.toList + def isSamePosition(msg: Message, cmsg: SnippetCompilerMessage): Boolean = + cmsg.level == msg.level && cmsg.position.get.line == msg.offset.line && cmsg.position.get.column == msg.offset.column + + def checkRelativeLines(msg: Message, cmsg: SnippetCompilerMessage): Seq[String] = + val pos = cmsg.position.get + if debug then { + if !(pos.relativeLine == pos.line - ws.outerLineOffset + ws.innerLineOffset) then Seq( + s"Expected ${msg.level.text} message at relative line: ${pos.line - ws.outerLineOffset + ws.innerLineOffset} " + + s"but found at ${pos.relativeLine}" + ) else Nil + } else { + if !(pos.relativeLine == pos.line - ws.outerLineOffset) then Seq( + s"Expected ${msg.level.text} message at relative line: ${pos.line - ws.outerLineOffset} " + + s"but found at ${pos.relativeLine}" + ) else Nil + } + + val result = messages.flatMap { msg => + compilationMessagesWithPos + .find(cmsg => isSamePosition(msg, cmsg)) + .fold(Seq(s"Expected ${msg.level.text} message at ${msg.offset.line}:${msg.offset.column}.")) { resp => + checkRelativeLines(msg, resp) + } + } + + if !result.isEmpty then { + val errors = result.mkString("\n") + val foundMessages = compilationMessages.map(m => s"${m.level} at ${m.position.get.line}:${m.position.get.column}").mkString("\n") + throw AssertionError(Seq("Errors:", errors,"Found:", foundMessages).mkString("\n", "\n", "\n")) + } + } + + def moduleTestingFunc: DocContext ?=> Module => Unit = (m: Module) => { + val snippets = m.members.values + .flatMap(_.docs) + .map(_.body) + .collect { case n: mdu.Node => n } + .flatMap(_.getDescendants.asScala) + .collect { case en: ExtendedFencedCodeBlock => en } + + assertTrue(report(s"Expected $snippetsCount snippets but found ${snippets.size}"), snippets.size == snippetsCount) + + snippets.foreach { snippet => + val configStrs = (snippet.getPrevious() match { + case c: mdu.ContentNode => + c.getContentChars.toString.split("\n").map(_.trim) + case _ => throw AssertionError(s"Not found info for snippet ${snippet.codeBlock.getContentChars.toString}") + }).toList + val info = SnippetInfo(configStrs.head) + val messages = configStrs.tail.map(Message.apply) + val compilationResult = snippet.compilationResult match { + case Some(res) => res + case None => throw AssertionError(s"Snippet validation failed:\n${snippet.codeBlock.getContentChars.toString}") + } + val wrappedSnippet = compilationResult.wrappedSnippet + checkWrappedSnippet(wrappedSnippet, info) + checkMessages(compilationResult.messages, messages, wrappedSnippet) + } + } + + def runTest = { + org.junit.Assume.assumeTrue("Running on Windows", java.io.File.separatorChar == '/') + withModule(moduleTestingFunc) + } + +object SnippetsE2eTest: + case class Offset(line: Int, column: Int) + case class SnippetInfo(outerOffset: Offset, innerOffset: Offset) + case class Message(level: MessageLevel, offset: Offset) + object SnippetInfo: + def apply(str: String): SnippetInfo = str match { + case snippetInfoRegex(ol, oc, il, ic) => SnippetInfo( + Offset(ol.toInt, oc.toInt), + Offset(il.toInt, ic.toInt) + ) + } + + object Message: + def apply(str: String): Message = str match { + case errorRegex(ln, cl) => Message(MessageLevel.Error, Offset(ln.toInt, cl.toInt)) + case warningRegex(ln, cl) => Message(MessageLevel.Warning, Offset(ln.toInt, cl.toInt)) + } + val snippetInfoRegex = (raw"SNIPPET\(" + + raw"OUTERLINEOFFSET:(\d+),OUTERCOLUMNOFFSET:(\d+)," + + raw"INNERLINEOFFSET:(\d+),INNERCOLUMNOFFSET:(\d+)\)").r + + val warningRegex = raw"WARNING\(LINE:(\d+),COLUMN:(\d+)\)".r + val errorRegex = raw"ERROR\(LINE:(\d+),COLUMN:(\d+)\)".r \ No newline at end of file diff --git a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTestcases.scala b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTestcases.scala new file mode 100644 index 000000000000..f1d588da81b5 --- /dev/null +++ b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTestcases.scala @@ -0,0 +1,10 @@ +package dotty.tools.scaladoc +package snippets + +class SnippetE2eTestcase1 extends SnippetsE2eTest("snippetTestcase1", SCFlags.Compile, false) + +class SnippetE2eTestcase1Debug extends SnippetsE2eTest("snippetTestcase1", SCFlags.Compile, true) + +class SnippetE2eTestcase2 extends SnippetsE2eTest("snippetTestcase2", SCFlags.Compile, false) + +class SnippetE2eTestcase2Debug extends SnippetsE2eTest("snippetTestcase2", SCFlags.Compile, true) diff --git a/scaladoc/test/dotty/tools/scaladoc/testUtils.scala b/scaladoc/test/dotty/tools/scaladoc/testUtils.scala index b5c3d2abd2bc..d0d23fdd0a82 100644 --- a/scaladoc/test/dotty/tools/scaladoc/testUtils.scala +++ b/scaladoc/test/dotty/tools/scaladoc/testUtils.scala @@ -53,7 +53,8 @@ class TestReporter extends ConsoleReporter: def testArgs(files: Seq[File] = Nil, dest: File = new File("notUsed")) = Scaladoc.Args( name = "Test Project Name", output = dest, - tastyFiles = files + tastyFiles = files, + docsRoot = Some("") ) def testContext = (new ContextBase).initialCtx.fresh.setReporter(new TestReporter) From 3b0e11e508fc7316748336f98c86be5c03af4314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Thu, 29 Apr 2021 14:36:54 +0200 Subject: [PATCH 24/28] Wrap compiler messages into Try block to avoid lazy exceptions --- .../dotty/tools/scaladoc/snippets/SnippetCompiler.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index d50d64fc19bc..c4d0d5097432 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -18,6 +18,8 @@ import dotty.tools.repl.AbstractFileClassLoader import dotty.tools.dotc.util.SourceFile import dotty.tools.dotc.interfaces.Diagnostic._ +import scala.util.{ Try, Success, Failure } + class SnippetCompiler( classpath: String, val scalacOptions: String = "", @@ -54,7 +56,11 @@ class SnippetCompiler( val pos = Some( Position(diagPos.line + line - innerLineOffset, diagPos.column + column - innerColumnOffset, diagPos.lineContent, if arg.debug then diagPos.line else diagPos.line - innerLineOffset) ) - val msg = nullableMessage(diagnostic.message) + val dmsg = Try(diagnostic.message) match { + case Success(msg) => msg + case Failure(ex) => ex.getMessage + } + val msg = nullableMessage(dmsg) val level = MessageLevel.fromOrdinal(diagnostic.level) SnippetCompilerMessage(pos, msg, level) case d => From 975065b6397980d742ed3231c006af7e74cff609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Tue, 11 May 2021 14:44:53 +0200 Subject: [PATCH 25/28] Update branch --- .../scaladoc/snippets/SnippetCompilerDataCollector.scala | 5 ++--- scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala | 4 ---- scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala | 5 +++++ .../src/dotty/tools/scaladoc/tasty/comments/Comments.scala | 3 ++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala index ae52b5e4c204..475d38fef895 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala @@ -2,13 +2,12 @@ package dotty.tools.scaladoc package snippets import scala.quoted._ -import dotty.tools.scaladoc.tasty.SymOps +import dotty.tools.scaladoc.tasty.SymOps._ import dotty.tools.dotc.core._ class SnippetCompilerDataCollector[Q <: Quotes](val qctx: Q): import qctx.reflect._ - object SymOps extends SymOps[qctx.type](qctx) - export SymOps._ + given qctx.type = qctx def getSnippetCompilerData(sym: Symbol, originalSym: Symbol): SnippetCompilerData = val packageName = sym.packageName diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala index 77cf553c82c9..bbfe50f421ec 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala @@ -39,10 +39,6 @@ trait BasicSupport: extension (using Quotes)(sym: reflect.Symbol) def documentation = sym.docstring.map(parseComment(_, sym.tree)) - def source = - val path = sym.pos.map(_.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath) - path.map(TastyMemberSource(_, sym.pos.get.startLine)) - def getAnnotations(): List[Annotation] = sym.annotations.filterNot(_.symbol.packageName.startsWith("scala.annotation.internal")).map(parseAnnotation).reverse diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index cdb1e889b29c..72c8a8073d8a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -38,6 +38,11 @@ object SymOps: Some(s"${sym.name}-$hash") } else None + + def source = + val path = sym.pos.map(_.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath) + path.map(TastyMemberSource(_, sym.pos.get.startLine)) + //TODO: Retrieve string that will match scaladoc anchors diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index ef08dbbf3f7f..066b7b5173c4 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -13,7 +13,7 @@ import scala.quoted._ import dotty.tools.scaladoc.tasty.comments.markdown.ExtendedFencedCodeBlock import dotty.tools.scaladoc.tasty.comments.wiki.Paragraph import dotty.tools.scaladoc.DocPart -import dotty.tools.scaladoc.tasty.SymOpsWithLinkCache +import dotty.tools.scaladoc.tasty.{ SymOpsWithLinkCache, SymOps } import collection.JavaConverters._ import dotty.tools.scaladoc.snippets._ @@ -89,6 +89,7 @@ abstract class MarkupConversion[T](val repr: Repr)(using dctx: DocContext) { object SymOpsWithLinkCache extends SymOpsWithLinkCache export SymOpsWithLinkCache._ + import SymOps._ def resolveLink(queryStr: String): DocLink = if SchemeUri.matches(queryStr) then DocLink.ToURL(queryStr) From 7d0e477e887ed1dee3793ea5286af8d1f00677ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Tue, 11 May 2021 14:50:02 +0200 Subject: [PATCH 26/28] Fix classpath problems. Add support for scala.js. Add bootclasspath to generateDocumentation task --- project/Build.scala | 18 +++++++++++++++--- .../src/dotty/tools/scaladoc/DocContext.scala | 2 +- .../src/dotty/tools/scaladoc/Scaladoc.scala | 2 ++ .../tools/scaladoc/ScaladocSettings.scala | 3 ++- .../scaladoc/snippets/SnippetChecker.scala | 15 +++++++++++---- .../scaladoc/snippets/SnippetCompiler.scala | 1 + .../dotty/tools/scaladoc/BaseHtmlTest.scala | 1 + .../scaladoc/snippets/SnippetsE2eTest.scala | 3 ++- .../test/dotty/tools/scaladoc/testUtils.scala | 3 ++- 9 files changed, 37 insertions(+), 11 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 4b8d78bdf357..5d6e2d703788 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1243,16 +1243,26 @@ object Build { libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "1.1.0").cross(CrossVersion.for3Use2_13) ) - def generateDocumentation(targets: Seq[String], name: String, outDir: String, ref: String, params: Seq[String] = Nil) = + def generateDocumentation(targets: Seq[String], name: String, outDir: String, ref: String, params: Seq[String] = Nil, addBootclasspath: Boolean = false) = Def.taskDyn { val distLocation = (dist / pack).value val projectVersion = version.value IO.createDirectory(file(outDir)) val stdLibVersion = stdlibVersion(Bootstrapped) + val scalaLib = findArtifactPath(externalCompilerClasspathTask.value, "scala-library") + val dottyLib = (`scala3-library` / Compile / classDirectory).value // TODO add versions etc. def srcManaged(v: String, s: String) = s"out/bootstrap/stdlib-bootstrapped/scala-$v/src_managed/main/$s-library-src" def scalaSrcLink(v: String, s: String) = s"-source-links:$s=github://scala/scala/v$v#src/library" def dottySrcLink(v: String, s: String) = s"-source-links:$s=github://lampepfl/dotty/$v#library/src" + def bootclasspath: Seq[String] = if(addBootclasspath) Seq( + "-bootclasspath", + Seq( + scalaLib, + dottyLib + ).mkString(System.getProperty("path.separator")) + ) else Nil + val revision = Seq("-revision", ref, "-project-version", projectVersion) val cmd = Seq( "-d", @@ -1262,7 +1272,7 @@ object Build { scalaSrcLink(stdLibVersion, srcManaged(dottyNonBootstrappedVersion, "scala")), dottySrcLink(referenceVersion, srcManaged(dottyNonBootstrappedVersion, "dotty")), s"-source-links:github://lampepfl/dotty/$referenceVersion", - ) ++ scalacOptionsDocSettings ++ revision ++ params ++ targets + ) ++ scalacOptionsDocSettings ++ revision ++ params ++ targets ++ bootclasspath import _root_.scala.sys.process._ Def.task((s"$distLocation/bin/scaladoc" +: cmd).!) } @@ -1312,6 +1322,7 @@ object Build { generateDocumentation( (Compile / classDirectory).value.getAbsolutePath :: Nil, "scaladoc", "scaladoc/output/self", VersionUtil.gitHash, + addBootclasspath = true ) }.value, generateScalaDocumentation := Def.inputTaskDyn { @@ -1364,7 +1375,8 @@ object Build { (Test / Build.testcasesOutputDir).value, "scaladoc testcases", "scaladoc/output/testcases", - "master") + "master", + addBootclasspath = true) }.value, Test / buildInfoKeys := Seq[BuildInfoKey]( diff --git a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala index 02acf651b127..d66aa9666e86 100644 --- a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala @@ -76,7 +76,7 @@ case class DocContext(args: Scaladoc.Args, compilerContext: CompilerContext): lazy val snippetCompilerArgs = snippets.SnippetCompilerArgs.load(args.snippetCompiler, args.snippetCompilerDebug)(using compilerContext) - lazy val snippetChecker = snippets.SnippetChecker(args.classpath, args.tastyDirs) + lazy val snippetChecker = snippets.SnippetChecker(args.classpath, args.bootclasspath, args.tastyFiles, compilerContext.settings.scalajs.value(using compilerContext)) lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext( File(path).getAbsoluteFile(), diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala index f227a33aa561..44dcb15b65b2 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala @@ -31,6 +31,7 @@ object Scaladoc: tastyDirs: Seq[File] = Nil, tastyFiles: Seq[File] = Nil, classpath: String = "", + bootclasspath: String = "", output: File, docsRoot: Option[String] = None, projectVersion: Option[String] = None, @@ -174,6 +175,7 @@ object Scaladoc: dirs, validFiles, classpath.get, + bootclasspath.get, destFile, siteRoot.nonDefault, projectVersion.nonDefault, diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala index cbe8d8215214..d186d41d84f6 100644 --- a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala +++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala @@ -19,13 +19,14 @@ import dotty.tools.dotc.core.Contexts._ class ScaladocSettings extends SettingGroup with AllScalaSettings: val unsupportedSettings = Seq( // Options that we like to support - bootclasspath, extdirs, javabootclasspath, encoding, usejavacp, + extdirs, javabootclasspath, encoding, usejavacp, // Needed for plugin architecture plugin,disable,require, pluginsDir, pluginOptions, // we need support for sourcepath and sourceroot sourcepath, sourceroot ) + val projectName: Setting[String] = StringSetting("-project", "project title", "The name of the project.", "", aliases = List("-doc-title")) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index f61057c39233..51582f9647d3 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -5,16 +5,23 @@ import dotty.tools.scaladoc.DocContext import java.nio.file.Paths import java.io.File -class SnippetChecker(val classpath: String, val tastyDirs: Seq[File]): +import dotty.tools.io.AbstractFile +import dotty.tools.dotc.fromtasty.TastyFileUtil + +class SnippetChecker(val classpath: String, val bootclasspath: String, val tastyFiles: Seq[File], isScalajs: Boolean): private val sep = System.getProperty("path.separator") private val cp = List( - tastyDirs.map(_.getAbsolutePath()).mkString(sep), + tastyFiles + .map(_.getAbsolutePath()) + .map(AbstractFile.getFile(_)) + .flatMap(t => try { TastyFileUtil.getClassPath(t) } catch { case e: AssertionError => Seq() }) + .distinct.mkString(sep), classpath, - System.getProperty("java.class.path") + bootclasspath ).mkString(sep) - private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp) + private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp, scalacOptions = if isScalajs then "-scalajs" else "") // These constants were found empirically to make snippet compiler // report errors in the same position as main compiler. diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index c4d0d5097432..93696e268e9a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -32,6 +32,7 @@ class SnippetCompiler( val options = scalacOptions.split("\\s+").toList val settings = options ::: defaultFlags ::: "-classpath" :: classpath :: Nil + new InteractiveDriver(settings) } diff --git a/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala b/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala index 6bb1f1a20010..3e1e10978785 100644 --- a/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala @@ -35,6 +35,7 @@ class BaseHtmlTest: output = dest.toFile, docsRoot = docsRoot, projectVersion = Some(projectVersion), + bootclasspath = dotty.tools.dotc.util.ClasspathFromClassloader(classOf[Predef$].getClassLoader()) ) Scaladoc.run(args)(using testContext) op(using ProjectContext(dest)) diff --git a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala index f80b709f3ce9..ad5ef29dd5c1 100644 --- a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala @@ -38,7 +38,8 @@ abstract class SnippetsE2eTest(testName: String, flag: SCFlags, debug: Boolean) output = getTempDir().getRoot, projectVersion = Some("1.0"), snippetCompiler = List(s"${BuildInfo.test_testcasesSourceRoot}/tests=${flag.flagName}"), - snippetCompilerDebug = debug + snippetCompilerDebug = debug, + bootclasspath = dotty.tools.dotc.util.ClasspathFromClassloader(classOf[Predef$].getClassLoader()) ) override def withModule(op: DocContext ?=> Module => Unit) = diff --git a/scaladoc/test/dotty/tools/scaladoc/testUtils.scala b/scaladoc/test/dotty/tools/scaladoc/testUtils.scala index d0d23fdd0a82..9b6b084c88be 100644 --- a/scaladoc/test/dotty/tools/scaladoc/testUtils.scala +++ b/scaladoc/test/dotty/tools/scaladoc/testUtils.scala @@ -54,7 +54,8 @@ def testArgs(files: Seq[File] = Nil, dest: File = new File("notUsed")) = Scalado name = "Test Project Name", output = dest, tastyFiles = files, - docsRoot = Some("") + docsRoot = Some(""), + bootclasspath = dotty.tools.dotc.util.ClasspathFromClassloader(classOf[Predef$].getClassLoader()) ) def testContext = (new ContextBase).initialCtx.fresh.setReporter(new TestReporter) From ac8531dfdaa815159ec7d5182e8c59b934eb940b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 17 May 2021 11:48:08 +0200 Subject: [PATCH 27/28] Temporarily turn off snippet checking for scala3 library --- project/Build.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 5d6e2d703788..e2e7f3353fd4 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1362,10 +1362,7 @@ object Build { "-siteroot", "docs", s"-source-links:docs=github://lampepfl/dotty/master#docs", "-doc-root-content", docRootFile.toString, - "-Ydocument-synthetic-types", - "-snippet-compiler:" + - s"$dottyLibRoot=compile," + - "docs=compile" + "-Ydocument-synthetic-types" ) )) }.evaluated, From 946752fd76b721393bc2a1fedcde1817da64bf5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Thu, 20 May 2021 11:41:15 +0200 Subject: [PATCH 28/28] Change requests from reviews --- dist/bin/scaladoc | 1 + project/Build.scala | 35 +++++++++------- .../src/dotty/tools/scaladoc/DocContext.scala | 2 +- .../tools/scaladoc/ScaladocSettings.scala | 4 +- .../scaladoc/snippets/SnippetChecker.scala | 19 ++++++--- .../scaladoc/snippets/SnippetCompiler.scala | 40 ++++++++++--------- .../snippets/SnippetCompilerSetting.scala | 8 ++++ .../comments/markdown/SnippetRenderer.scala | 6 +-- .../dotty/tools/scaladoc/BaseHtmlTest.scala | 3 +- .../snippets/SnippetCompilerTest.scala | 4 +- .../scaladoc/snippets/SnippetsE2eTest.scala | 3 +- .../test/dotty/tools/scaladoc/testUtils.scala | 6 ++- 12 files changed, 79 insertions(+), 52 deletions(-) create mode 100644 scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerSetting.scala diff --git a/dist/bin/scaladoc b/dist/bin/scaladoc index e92e21d6e49a..f09bf27812c0 100755 --- a/dist/bin/scaladoc +++ b/dist/bin/scaladoc @@ -127,6 +127,7 @@ eval "\"$JAVACMD\"" \ ${JAVA_OPTS:-$default_java_opts} \ "${java_args[@]}" \ "${jvm_cp_args-}" \ + -Dscala.usejavacp=true \ "dotty.tools.scaladoc.Main" \ "${scala_args[@]}" \ "${residual_args[@]}" \ diff --git a/project/Build.scala b/project/Build.scala index e2e7f3353fd4..52ff8f8979e5 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1243,7 +1243,7 @@ object Build { libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "1.1.0").cross(CrossVersion.for3Use2_13) ) - def generateDocumentation(targets: Seq[String], name: String, outDir: String, ref: String, params: Seq[String] = Nil, addBootclasspath: Boolean = false) = + def generateDocumentation(targets: Seq[String], name: String, outDir: String, ref: String, params: Seq[String] = Nil, usingScript: Boolean = true) = Def.taskDyn { val distLocation = (dist / pack).value val projectVersion = version.value @@ -1255,13 +1255,6 @@ object Build { def srcManaged(v: String, s: String) = s"out/bootstrap/stdlib-bootstrapped/scala-$v/src_managed/main/$s-library-src" def scalaSrcLink(v: String, s: String) = s"-source-links:$s=github://scala/scala/v$v#src/library" def dottySrcLink(v: String, s: String) = s"-source-links:$s=github://lampepfl/dotty/$v#library/src" - def bootclasspath: Seq[String] = if(addBootclasspath) Seq( - "-bootclasspath", - Seq( - scalaLib, - dottyLib - ).mkString(System.getProperty("path.separator")) - ) else Nil val revision = Seq("-revision", ref, "-project-version", projectVersion) val cmd = Seq( @@ -1272,9 +1265,22 @@ object Build { scalaSrcLink(stdLibVersion, srcManaged(dottyNonBootstrappedVersion, "scala")), dottySrcLink(referenceVersion, srcManaged(dottyNonBootstrappedVersion, "dotty")), s"-source-links:github://lampepfl/dotty/$referenceVersion", - ) ++ scalacOptionsDocSettings ++ revision ++ params ++ targets ++ bootclasspath + ) ++ scalacOptionsDocSettings ++ revision ++ params ++ targets import _root_.scala.sys.process._ - Def.task((s"$distLocation/bin/scaladoc" +: cmd).!) + if (usingScript) + Def.task((s"$distLocation/bin/scaladoc" +: cmd).!) + else { + val escapedCmd = cmd.map(arg => if(arg.contains(" ")) s""""$arg"""" else arg) + Def.task { + try { + (Compile / run).toTask(escapedCmd.mkString(" ", " ", "")).value + 0 + } catch { + case _ : Throwable => 1 + } + } + } + } val SourceLinksIntegrationTest = config("sourceLinksIntegrationTest") extend Test @@ -1321,8 +1327,7 @@ object Build { generateSelfDocumentation := Def.taskDyn { generateDocumentation( (Compile / classDirectory).value.getAbsolutePath :: Nil, - "scaladoc", "scaladoc/output/self", VersionUtil.gitHash, - addBootclasspath = true + "scaladoc", "scaladoc/output/self", VersionUtil.gitHash ) }.value, generateScalaDocumentation := Def.inputTaskDyn { @@ -1363,7 +1368,7 @@ object Build { s"-source-links:docs=github://lampepfl/dotty/master#docs", "-doc-root-content", docRootFile.toString, "-Ydocument-synthetic-types" - ) + ), usingScript = false )) }.evaluated, @@ -1372,8 +1377,8 @@ object Build { (Test / Build.testcasesOutputDir).value, "scaladoc testcases", "scaladoc/output/testcases", - "master", - addBootclasspath = true) + "master" + ) }.value, Test / buildInfoKeys := Seq[BuildInfoKey]( diff --git a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala index d66aa9666e86..ee1444effc4a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala @@ -76,7 +76,7 @@ case class DocContext(args: Scaladoc.Args, compilerContext: CompilerContext): lazy val snippetCompilerArgs = snippets.SnippetCompilerArgs.load(args.snippetCompiler, args.snippetCompilerDebug)(using compilerContext) - lazy val snippetChecker = snippets.SnippetChecker(args.classpath, args.bootclasspath, args.tastyFiles, compilerContext.settings.scalajs.value(using compilerContext)) + lazy val snippetChecker = snippets.SnippetChecker(args)(using compilerContext) lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext( File(path).getAbsoluteFile(), diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala index d186d41d84f6..ec326bf57178 100644 --- a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala +++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala @@ -19,7 +19,7 @@ import dotty.tools.dotc.core.Contexts._ class ScaladocSettings extends SettingGroup with AllScalaSettings: val unsupportedSettings = Seq( // Options that we like to support - extdirs, javabootclasspath, encoding, usejavacp, + extdirs, javabootclasspath, encoding, // Needed for plugin architecture plugin,disable,require, pluginsDir, pluginOptions, // we need support for sourcepath and sourceroot @@ -100,7 +100,7 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings: MultiStringSetting("-snippet-compiler", "snippet-compiler", snippets.SnippetCompilerArgs.usage) val snippetCompilerDebug: Setting[Boolean] = - BooleanSetting("-snippet-compiler-debug", snippets.SnippetCompilerArgs.debugUsage, false) + BooleanSetting("-Ysnippet-compiler-debug", snippets.SnippetCompilerArgs.debugUsage, false) def scaladocSpecificSettings: Set[Setting[_]] = Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, snippetCompilerDebug) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index 51582f9647d3..763821d0de59 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -7,21 +7,28 @@ import java.io.File import dotty.tools.io.AbstractFile import dotty.tools.dotc.fromtasty.TastyFileUtil +import dotty.tools.dotc.config.Settings._ +import dotty.tools.dotc.config.ScalaSettings -class SnippetChecker(val classpath: String, val bootclasspath: String, val tastyFiles: Seq[File], isScalajs: Boolean): +class SnippetChecker(val args: Scaladoc.Args)(using cctx: CompilerContext): + +// (val classpath: String, val bootclasspath: String, val tastyFiles: Seq[File], isScalajs: Boolean, useJavaCp: Boolean): private val sep = System.getProperty("path.separator") - private val cp = List( - tastyFiles + + private val fullClasspath = List( + args.tastyFiles .map(_.getAbsolutePath()) .map(AbstractFile.getFile(_)) .flatMap(t => try { TastyFileUtil.getClassPath(t) } catch { case e: AssertionError => Seq() }) .distinct.mkString(sep), - classpath, - bootclasspath + args.classpath ).mkString(sep) + private val snippetCompilerSettings: Seq[SnippetCompilerSetting[_]] = cctx.settings.userSetSettings(cctx.settingsState).filter(_ != cctx.settings.classpath).map( s => + SnippetCompilerSetting(s, s.valueIn(cctx.settingsState)) + ) :+ SnippetCompilerSetting(cctx.settings.classpath, fullClasspath) - private val compiler: SnippetCompiler = SnippetCompiler(classpath = cp, scalacOptions = if isScalajs then "-scalajs" else "") + private val compiler: SnippetCompiler = SnippetCompiler(snippetCompilerSettings = snippetCompilerSettings) // These constants were found empirically to make snippet compiler // report errors in the same position as main compiler. diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index 93696e268e9a..9fdcfc8e46cf 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -2,10 +2,9 @@ package dotty.tools.scaladoc package snippets import dotty.tools.io.{AbstractFile, VirtualDirectory} -import dotty.tools.dotc.interactive.InteractiveDriver -import dotty.tools.dotc.interactive.Interactive -import dotty.tools.dotc.interactive.InteractiveCompiler +import dotty.tools.dotc.Driver import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Mode import dotty.tools.dotc.config.Settings.Setting._ import dotty.tools.dotc.interfaces.SourcePosition import dotty.tools.dotc.ast.Trees.Tree @@ -21,22 +20,27 @@ import dotty.tools.dotc.interfaces.Diagnostic._ import scala.util.{ Try, Success, Failure } class SnippetCompiler( - classpath: String, - val scalacOptions: String = "", + val snippetCompilerSettings: Seq[SnippetCompilerSetting[_]], target: AbstractFile = new VirtualDirectory("(memory)") ): + object SnippetDriver extends Driver: + val currentCtx = + val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive) + rootCtx.setSetting(rootCtx.settings.YretainTrees, true) + rootCtx.setSetting(rootCtx.settings.YcookComments, true) + rootCtx.setSetting(rootCtx.settings.YreadComments, true) + rootCtx.setSetting(rootCtx.settings.color, "never") + rootCtx.setSetting(rootCtx.settings.XimportSuggestionTimeout, 0) - private def newDriver: InteractiveDriver = { - val defaultFlags = - List("-color:never", "-unchecked", "-deprecation", "-Ximport-suggestion-timeout", "0") - val options = scalacOptions.split("\\s+").toList - val settings = - options ::: defaultFlags ::: "-classpath" :: classpath :: Nil - - new InteractiveDriver(settings) - } - - private val driver = newDriver + val ctx = setup(Array(""), rootCtx) match + case Some((_, ctx)) => + ctx + case None => rootCtx + val res = snippetCompilerSettings.foldLeft(ctx.fresh) { (ctx, setting) => + ctx.setSetting(setting.setting, setting.value) + } + res.initialize()(using res) + res private val scala3Compiler = new Compiler @@ -86,9 +90,9 @@ class SnippetCompiler( wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg ): SnippetCompilationResult = { - val context = driver.currentCtx.fresh + val context = SnippetDriver.currentCtx.fresh .setSetting( - driver.currentCtx.settings.outputDir, + SnippetDriver.currentCtx.settings.outputDir, target ) .setReporter(new StoreReporter) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerSetting.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerSetting.scala new file mode 100644 index 000000000000..4c5ef500c83a --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerSetting.scala @@ -0,0 +1,8 @@ +package dotty.tools.scaladoc +package snippets + +import dotty.tools.scaladoc.DocContext +import dotty.tools.dotc.config.Settings._ +import dotty.tools.dotc.config.ScalaSettings + +case class SnippetCompilerSetting[T](setting: Setting[T], value: T) \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala index 39fe63f8a3b3..0c480fe5ced9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala @@ -18,7 +18,7 @@ object SnippetRenderer: case MessageLevel.Error => "snippet-error" case MessageLevel.Debug => "snippet-debug" - private def cutBetwenSymbols[A]( + private def cutBetweenSymbols[A]( startSymbol: String, endSymbol: String, snippetLines: Seq[SnippetLine] @@ -33,7 +33,7 @@ object SnippetRenderer: } yield f(begin, mid, end) private def wrapHiddenSymbols(snippetLines: Seq[SnippetLine]): Seq[SnippetLine] = - val mRes = cutBetwenSymbols("//{", "//}", snippetLines) { + val mRes = cutBetweenSymbols("//{", "//}", snippetLines) { case (begin, mid, end) => begin ++ mid.drop(1).dropRight(1).map(_.withClass("hideable")) ++ wrapHiddenSymbols(end) } @@ -74,7 +74,7 @@ object SnippetRenderer: } private def wrapMultiLineComments(snippetLines: Seq[SnippetLine]): Seq[SnippetLine] = - val mRes = cutBetwenSymbols("/*", "*/", snippetLines) { + val mRes = cutBetweenSymbols("/*", "*/", snippetLines) { case (begin, mid, end) if mid.size == 1 => val midRedacted = mid.map(wrapLineInBetween(Some("/*"), Some("*/"), _)) begin ++ midRedacted ++ end diff --git a/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala b/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala index 3e1e10978785..82713d0505f7 100644 --- a/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala @@ -34,8 +34,7 @@ class BaseHtmlTest: tastyFiles = pcks.flatMap(tastyFiles(_)), output = dest.toFile, docsRoot = docsRoot, - projectVersion = Some(projectVersion), - bootclasspath = dotty.tools.dotc.util.ClasspathFromClassloader(classOf[Predef$].getClassLoader()) + projectVersion = Some(projectVersion) ) Scaladoc.run(args)(using testContext) op(using ProjectContext(dest)) diff --git a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala index 65aca6021b71..86a630a9d42f 100644 --- a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala @@ -6,7 +6,9 @@ import org.junit.Assert._ import dotty.tools.io.{AbstractFile, VirtualDirectory} class SnippetCompilerTest { - val compiler = SnippetCompiler(System.getProperty("java.class.path")) + val compiler = SnippetCompiler( + Seq(SnippetCompilerSetting(testContext.settings.usejavacp, true)) + ) def wrapFn: String => WrappedSnippet = (str: String) => WrappedSnippet( str, Some("test"), diff --git a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala index ad5ef29dd5c1..f80b709f3ce9 100644 --- a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala @@ -38,8 +38,7 @@ abstract class SnippetsE2eTest(testName: String, flag: SCFlags, debug: Boolean) output = getTempDir().getRoot, projectVersion = Some("1.0"), snippetCompiler = List(s"${BuildInfo.test_testcasesSourceRoot}/tests=${flag.flagName}"), - snippetCompilerDebug = debug, - bootclasspath = dotty.tools.dotc.util.ClasspathFromClassloader(classOf[Predef$].getClassLoader()) + snippetCompilerDebug = debug ) override def withModule(op: DocContext ?=> Module => Unit) = diff --git a/scaladoc/test/dotty/tools/scaladoc/testUtils.scala b/scaladoc/test/dotty/tools/scaladoc/testUtils.scala index 9b6b084c88be..5d2191c22c40 100644 --- a/scaladoc/test/dotty/tools/scaladoc/testUtils.scala +++ b/scaladoc/test/dotty/tools/scaladoc/testUtils.scala @@ -55,10 +55,12 @@ def testArgs(files: Seq[File] = Nil, dest: File = new File("notUsed")) = Scalado output = dest, tastyFiles = files, docsRoot = Some(""), - bootclasspath = dotty.tools.dotc.util.ClasspathFromClassloader(classOf[Predef$].getClassLoader()) ) -def testContext = (new ContextBase).initialCtx.fresh.setReporter(new TestReporter) +def testContext = + val ctx = (new ContextBase).initialCtx.fresh.setReporter(new TestReporter) + ctx.setSetting(ctx.settings.usejavacp, true) + ctx def testDocContext(files: Seq[File] = Nil) = DocContext(testArgs(files), testContext)