diff --git a/mdoc-docs/src/main/scala/mdoc/docs/MdocModifier.scala b/mdoc-docs/src/main/scala/mdoc/docs/MdocModifier.scala index ef627820..f2a601ba 100644 --- a/mdoc-docs/src/main/scala/mdoc/docs/MdocModifier.scala +++ b/mdoc-docs/src/main/scala/mdoc/docs/MdocModifier.scala @@ -23,8 +23,16 @@ class MdocModifier(context: Context) extends StringModifier { myStdout.reset() myReporter.reset() val cleanInput = Input.VirtualFile(code.filename, code.text) - val markdown = Markdown.toMarkdown(cleanInput, markdownSettings, myReporter, context.settings) - val links = DocumentLinks.fromMarkdown(GitHubIdGenerator, RelativePath("readme.md"), cleanInput) + val relpath = RelativePath("readme.md") + val markdown = Markdown.toMarkdown( + cleanInput, + context, + relpath, + Map.empty[String, String], + myReporter, + context.settings + ) + val links = DocumentLinks.fromMarkdown(GitHubIdGenerator, relpath, cleanInput) LinkHygiene.lint(List(links), myReporter, verbose = false) val stdout = fansi.Str(myStdout.toString()).plainText if (myReporter.hasErrors || myReporter.hasWarnings) { diff --git a/mdoc-sbt/src/sbt-test/sbt-mdoc/basic/build.sbt b/mdoc-sbt/src/sbt-test/sbt-mdoc/basic/build.sbt index 5ba3e8ba..34c60a60 100644 --- a/mdoc-sbt/src/sbt-test/sbt-mdoc/basic/build.sbt +++ b/mdoc-sbt/src/sbt-test/sbt-mdoc/basic/build.sbt @@ -18,11 +18,8 @@ println(example.Example.greeting) ```scala println("Hello Scala.js!") ``` -
- - """.trim, "\"\"\"\n" + obtained + "\n\"\"\"" diff --git a/mdoc/src/main/scala/mdoc/internal/cli/MainOps.scala b/mdoc/src/main/scala/mdoc/internal/cli/MainOps.scala index c93b6412..a1de7179 100644 --- a/mdoc/src/main/scala/mdoc/internal/cli/MainOps.scala +++ b/mdoc/src/main/scala/mdoc/internal/cli/MainOps.scala @@ -24,10 +24,10 @@ import scala.meta.io.AbsolutePath import scala.util.control.NonFatal final class MainOps( - settings: Settings, - markdown: MutableDataSet, - reporter: Reporter + context: Context ) { + def settings: Settings = context.settings + def reporter: Reporter = context.reporter private var livereload: Option[LiveReload] = None private def startLivereload(): Unit = { @@ -59,9 +59,7 @@ final class MainOps( val timer = new Timer val source = FileIO.slurp(file.in, settings.charset) val input = Input.VirtualFile(file.in.toString(), source) - markdown.set(Markdown.InputKey, Some(input)) - markdown.set(Markdown.RelativePathKey, Some(file.relpath)) - val md = Markdown.toMarkdown(input, markdown, reporter, settings) + val md = Markdown.toMarkdown(input, context, file.relpath, settings.site, reporter, settings) val fileHasErrors = reporter.errorCount > originalErrors if (!fileHasErrors) { writePath(file, md) @@ -226,11 +224,10 @@ object MainOps { error.all.foreach(message => reporter.error(message)) 1 case Configured.Ok(ctx) => - val markdown = Markdown.mdocSettings(ctx) if (ctx.settings.verbose) { ctx.reporter.setDebugEnabled(true) } - val runner = new MainOps(ctx.settings, markdown, ctx.reporter) + val runner = new MainOps(ctx) val exit = runner.run() if (exit.isSuccess) { 0 diff --git a/mdoc/src/main/scala/mdoc/internal/markdown/FenceInput.scala b/mdoc/src/main/scala/mdoc/internal/markdown/FenceInput.scala new file mode 100644 index 00000000..90d14cdf --- /dev/null +++ b/mdoc/src/main/scala/mdoc/internal/markdown/FenceInput.scala @@ -0,0 +1,101 @@ +package mdoc.internal.markdown + +import com.vladsch.flexmark.ast.FencedCodeBlock +import scala.meta.inputs.Input +import scala.meta.inputs.Position +import mdoc.internal.cli.Context +import mdoc.internal.markdown.Modifier.Str +import mdoc.internal.markdown.Modifier.Default +import mdoc.internal.markdown.Modifier.Post +import mdoc.internal.markdown.Modifier.Pre + +case class PreFenceInput(block: CodeFence, input: Input, mod: Pre) +case class StringFenceInput(block: CodeFence, input: Input, mod: Str) +case class ScalaFenceInput(block: CodeFence, input: Input, mod: Modifier) + +class FenceInput(ctx: Context, baseInput: Input) { + def getModifier(info: Text): Option[Modifier] = { + val string = info.value.stripLineEnd + if (!string.startsWith("scala mdoc")) None + else { + if (!string.contains(':')) Some(Modifier.Default()) + else { + val mode = string.stripPrefix("scala mdoc:") + Modifier(mode) + .orElse { + val (name, info) = mode.split(":", 2) match { + case Array(a) => (a, "") + case Array(a, b) => (a, b) + } + ctx.settings.stringModifiers + .collectFirst[Modifier] { + case mod if mod.name == name => + Str(mod, info) + } + .orElse { + ctx.settings.postModifiers.collectFirst { + case mod if mod.name == name => + Post(mod, info) + } + } + .orElse { + ctx.settings.preModifiers.collectFirst { + case mod if mod.name == name => + Pre(mod, info) + } + } + } + .orElse { + invalid(info, s"Invalid mode '$mode'") + None + } + } + } + } + + private def invalid(info: Text, message: String): Unit = { + val offset = "scala mdoc:".length + val start = info.pos.start + offset + val end = info.pos.end - 1 + val pos = Position.Range(baseInput, start, end) + ctx.reporter.error(pos, message) + } + private def invalidCombination(info: Text, mod1: String, mod2: String): Boolean = { + invalid(info, s"invalid combination of modifiers '$mod1' and '$mod2'") + false + } + + private def isValid(info: Text, mod: Modifier): Boolean = { + if (mod.isFail && mod.isCrash) { + invalidCombination(info, "crash", "fail") + } else if (mod.isSilent && mod.isInvisible) { + invalidCombination(info, "silent", "invisible") + } else if (mod.isCompileOnly) { + val others = mod.mods - Mod.CompileOnly + if (others.isEmpty) { + true + } else { + val all = others.map(_.toString.toLowerCase).mkString(", ") + invalid( + info, + s"""compile-only cannot be used in combination with $all""" + ) + false + } + } else { + true + } + } + def unapply(block: CodeFence): Option[ScalaFenceInput] = { + getModifier(block.info) match { + case Some(mod) => + if (isValid(block.info, mod)) { + val input = Input.Slice(baseInput, block.body.pos.start, block.body.pos.end) + Some(ScalaFenceInput(block, input, mod)) + } else { + None + } + case _ => None + } + } +} diff --git a/mdoc/src/main/scala/mdoc/internal/markdown/Markdown.scala b/mdoc/src/main/scala/mdoc/internal/markdown/Markdown.scala index ef5a4e64..f983218f 100644 --- a/mdoc/src/main/scala/mdoc/internal/markdown/Markdown.scala +++ b/mdoc/src/main/scala/mdoc/internal/markdown/Markdown.scala @@ -99,6 +99,26 @@ object Markdown { ) } + def toMarkdown( + input: Input, + context: Context, + relativePath: RelativePath, + siteVariables: Map[String, String], + reporter: Reporter, + settings: Settings + ): String = { + val textWithVariables = VariableRegex.replaceVariables( + input, + siteVariables, + reporter, + settings + ) + val file = MarkdownFile.parse(textWithVariables, relativePath, reporter) + val processor = new Processor()(context) + processor.processDocument(file) + file.renderToString + } + def toMarkdown( input: Input, markdownSettings: MutableDataSet, diff --git a/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownFile.scala b/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownFile.scala new file mode 100644 index 00000000..95086c8a --- /dev/null +++ b/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownFile.scala @@ -0,0 +1,128 @@ +package mdoc.internal.markdown + +import scala.meta.inputs.Position +import scala.meta.inputs.Input +import mdoc.Reporter +import scala.collection.mutable +import scala.tools.nsc.doc.DocParser +import scala.meta.io.RelativePath + +final case class MarkdownFile(input: Input, relativePath: RelativePath, parts: List[MarkdownPart]) { + private val appends = mutable.ListBuffer.empty[String] + def appendText(text: String): Unit = { + appends += text + } + def renderToString: String = { + val out = new StringBuilder() + parts.foreach(_.renderToString(out)) + appends.foreach(a => out.append(a).append("\n")) + out.toString() + } +} +object MarkdownFile { + sealed abstract class State + object State { + case class CodeFence(start: Int, backticks: String, info: String) extends State + case object Text extends State + } + class Parser(input: Input, reporter: Reporter) { + private val text = input.text + private def newPos(start: Int, end: Int): Position = { + Position.Range(input, start, end) + } + private def newText(start: Int, end: Int): Text = { + val adaptedEnd = math.max(start, end) + val part = Text(text.substring(start, adaptedEnd)) + part.pos = newPos(start, adaptedEnd) + part + } + private def newCodeFence( + state: State.CodeFence, + backtickStart: Int, + backtickEnd: Int + ): CodeFence = { + val open = newText(state.start, state.start + state.backticks.length()) + val info = newText(open.pos.end, open.pos.end + state.info.length()) + val adaptedBacktickStart = math.max(0, backtickStart - 1) + val body = newText(info.pos.end, adaptedBacktickStart) + val close = newText(adaptedBacktickStart, backtickEnd) + val part = CodeFence(open, info, body, close) + part.pos = newPos(state.start, backtickEnd) + part + } + def acceptParts(): List[MarkdownPart] = { + var state: State = State.Text + val parts = mutable.ListBuffer.empty[MarkdownPart] + var curr = 0 + text.linesWithSeparators.foreach { line => + val end = curr + line.length() + state match { + case State.Text => + if (line.startsWith("```")) { + val backticks = line.takeWhile(_ == '`') + val info = line.substring(backticks.length()) + state = State.CodeFence(curr, backticks, info) + } else { + parts += newText(curr, end) + } + case s: State.CodeFence => + if (line.startsWith(s.backticks) && + line.forall(ch => ch == '`' || ch.isWhitespace)) { + parts += newCodeFence(s, curr, end) + state = State.Text + } + } + curr += line.length() + } + state match { + case s: State.CodeFence => + parts += newCodeFence(s, text.length(), text.length()) + case _ => + } + parts.toList + } + } + def parse(input: Input, relativePath: RelativePath, reporter: Reporter): MarkdownFile = { + val parser = new Parser(input, reporter) + val parts = parser.acceptParts() + MarkdownFile(input, relativePath, parts) + } +} + +sealed abstract class MarkdownPart { + var pos: Position = Position.None + final def renderToString(out: StringBuilder): Unit = this match { + case Text(value) => + out.append(value) + case fence: CodeFence => + fence.newPart match { + case Some(newPart) => + out.append(newPart) + case None => + fence.openBackticks.renderToString(out) + fence.newInfo match { + case None => + fence.info.renderToString(out) + case Some(newInfo) => + out.append(newInfo) + if (!newInfo.endsWith("\n")) { + out.append("\n") + } + } + fence.newBody match { + case None => + fence.body.renderToString(out) + case Some(newBody) => + out.append(newBody) + } + fence.closeBackticks.renderToString(out) + } + } +} +final case class Text(value: String) extends MarkdownPart +final case class CodeFence(openBackticks: Text, info: Text, body: Text, closeBackticks: Text) + extends MarkdownPart { + var newPart = Option.empty[String] + var newInfo = Option.empty[String] + var newBody = Option.empty[String] +} diff --git a/mdoc/src/main/scala/mdoc/internal/markdown/MdocExtensions.scala b/mdoc/src/main/scala/mdoc/internal/markdown/MdocExtensions.scala index 81a08f48..c1fcc90a 100644 --- a/mdoc/src/main/scala/mdoc/internal/markdown/MdocExtensions.scala +++ b/mdoc/src/main/scala/mdoc/internal/markdown/MdocExtensions.scala @@ -24,10 +24,7 @@ object MdocExtensions { * @return A sequence of extensions to be applied to Flexmark's options. */ def mdoc(context: Context): List[Extension] = { - plain ++ List( - MdocParserExtension.create(context), - MdocFormatterExtension.create(context.settings) - ) + plain } def plain: List[Extension] = { diff --git a/mdoc/src/main/scala/mdoc/internal/markdown/MdocFormatterExtension.scala b/mdoc/src/main/scala/mdoc/internal/markdown/MdocFormatterExtension.scala deleted file mode 100644 index 49d731a7..00000000 --- a/mdoc/src/main/scala/mdoc/internal/markdown/MdocFormatterExtension.scala +++ /dev/null @@ -1,16 +0,0 @@ -package mdoc.internal.markdown - -import com.vladsch.flexmark.Extension -import com.vladsch.flexmark.formatter.Formatter -import com.vladsch.flexmark.util.options.MutableDataHolder -import mdoc.internal.cli.Settings - -// To be used later, it adds extensions to the formatter -class MdocFormatterExtension(options: Settings) extends Formatter.FormatterExtension { - override def rendererOptions(options: MutableDataHolder): Unit = () - override def extend(builder: Formatter.Builder): Unit = () -} - -object MdocFormatterExtension { - def create(options: Settings): Extension = new MdocFormatterExtension(options) -} diff --git a/mdoc/src/main/scala/mdoc/internal/markdown/MdocParserExtension.scala b/mdoc/src/main/scala/mdoc/internal/markdown/MdocParserExtension.scala deleted file mode 100644 index c171ec9a..00000000 --- a/mdoc/src/main/scala/mdoc/internal/markdown/MdocParserExtension.scala +++ /dev/null @@ -1,80 +0,0 @@ -package mdoc.internal.markdown - -import com.vladsch.flexmark.Extension -import com.vladsch.flexmark.ast.Text -import com.vladsch.flexmark.util.ast -import com.vladsch.flexmark.parser.LinkRefProcessor -import com.vladsch.flexmark.parser.LinkRefProcessorFactory -import com.vladsch.flexmark.parser.Parser -import com.vladsch.flexmark.util.options.DataHolder -import com.vladsch.flexmark.util.options.MutableDataHolder -import com.vladsch.flexmark.util.sequence.BasedSequence -import com.vladsch.flexmark.util.sequence.PrefixedSubSequence -import mdoc.internal.cli.Context - -class MdocParserExtension(context: Context) extends Parser.ParserExtension { - class SiteVariableInjector(site: Map[String, String], document: ast.Document) - extends LinkRefProcessor { - - /** - * Creates a text node with the value of a site variable. - * - * Using `PrefixedSubSequence` and then removing suffix is the only way I found to simulate - * `replace`. Converting from `String` to `BasedSequence` via `stringToCharSequence` does not - * work because Flexmark's `Text` only uses the start and end offsets and does not do an - * in-place replacement of the contents of the returned sequence. These start and end offsets - * select from the original text, not the modified value that we wanted to insert. - * Creating a custom node extending `ContentNode` or `CustomNode` did not help either. - */ - override def createNode(nodeChars: BasedSequence): ast.Node = { - nodeChars.toString match { - case VariableInjectionPattern(key) => - val value = site.getOrElse(key, sys.error(s"Missing '$key' site variable.")) - new Text(PrefixedSubSequence.of(value, nodeChars).removeSuffix(nodeChars)) - case _ => - sys.error("Flexmark matched a variable injection which is not of the expected shape.") - } - } - - private final val totalLength = document.getTextLength - private final val VariableInjectionPattern = "!\\[([^\\]\\[]*)\\]".r - override def isMatch(nodeChars: BasedSequence): Boolean = { - val matches = nodeChars.toString.matches(VariableInjectionPattern.regex) - val startNext = nodeChars.getEndOffset - val endNext = startNext + 1 - // As bracket nesting level is 0, we need to check that the following char does not start with `[` - matches && ( - startNext == totalLength || - !document.getChars.baseSubSequence(startNext, endNext).startsWith("[") - ) - } - - override def adjustInlineText(doc: ast.Document, node: ast.Node): BasedSequence = node.getChars - override def getBracketNestingLevel: Int = 0 - override def getWantExclamationPrefix: Boolean = true - override def updateNodeElements(document: ast.Document, node: ast.Node): Unit = () - override def allowDelimiters(chars: BasedSequence, doc: ast.Document, node: ast.Node): Boolean = - true - } - - class SiteVariableInjectorFactory extends LinkRefProcessorFactory { - override def getBracketNestingLevel(options: DataHolder): Int = 0 - override def getWantExclamationPrefix(options: DataHolder): Boolean = true - override def create(document: ast.Document): LinkRefProcessor = - new SiteVariableInjector(context.settings.site, document) - } - - override def extend(parserBuilder: Parser.Builder): Unit = { - parserBuilder.linkRefProcessorFactory(new SiteVariableInjectorFactory) - parserBuilder.postProcessorFactory( - new MdocPostProcessor.Factory(context) - ) - } - override def parserOptions(options: MutableDataHolder): Unit = () -} - -object MdocParserExtension { - def create(context: Context): Extension = { - new MdocParserExtension(context) - } -} diff --git a/mdoc/src/main/scala/mdoc/internal/markdown/MdocPostProcessor.scala b/mdoc/src/main/scala/mdoc/internal/markdown/Processor.scala similarity index 68% rename from mdoc/src/main/scala/mdoc/internal/markdown/MdocPostProcessor.scala rename to mdoc/src/main/scala/mdoc/internal/markdown/Processor.scala index 78a032e5..167bea20 100644 --- a/mdoc/src/main/scala/mdoc/internal/markdown/MdocPostProcessor.scala +++ b/mdoc/src/main/scala/mdoc/internal/markdown/Processor.scala @@ -2,8 +2,6 @@ package mdoc.internal.markdown import com.vladsch.flexmark.ast.FencedCodeBlock import com.vladsch.flexmark.util.ast -import com.vladsch.flexmark.util.ast.Document -import com.vladsch.flexmark.util.ast.Node import com.vladsch.flexmark.parser.block.DocumentPostProcessor import com.vladsch.flexmark.parser.block.DocumentPostProcessorFactory import com.vladsch.flexmark.util.options.MutableDataSet @@ -24,18 +22,13 @@ import mdoc.internal.pos.PositionSyntax._ import pprint.TPrintColors import scala.meta.io.RelativePath -class MdocPostProcessor(implicit ctx: Context) extends DocumentPostProcessor { +class Processor(implicit ctx: Context) { - override def processDocument(doc: Document): Document = { - val docInput = doc - .get(Markdown.InputKey) - .getOrElse(sys.error(s"Missing DataKey ${Markdown.InputKey}")) - val (scalaInputs, customInputs, preInputs) = collectBlockInputs(doc, docInput) + def processDocument(doc: MarkdownFile): MarkdownFile = { + val docInput = doc.input + val (scalaInputs, customInputs, preInputs) = collectFenceInputs(doc) val filename = docInput.toFilename(ctx.settings) - val inputFile = - doc - .get(Markdown.RelativePathKey) - .getOrElse(throw new NoSuchElementException(s"InputFile: $filename")) + val inputFile = doc.relativePath customInputs.foreach { block => processStringInput(doc, block) } @@ -65,15 +58,10 @@ class MdocPostProcessor(implicit ctx: Context) extends DocumentPostProcessor { } } - def postProcess(doc: Document, inputFile: RelativePath): Unit = {} - - def processPreInput(doc: Document, custom: PreBlockInput): Unit = { - val PreBlockInput(block, input, Pre(mod, info)) = custom + def processPreInput(doc: MarkdownFile, custom: PreFenceInput): Unit = { + val PreFenceInput(block, input, Pre(mod, info)) = custom try { - val inputFile = - doc - .get(Markdown.RelativePathKey) - .getOrElse(throw new NoSuchElementException(s"relativepath")) + val inputFile = doc.relativePath val preCtx = new PreModifierContext( info, input, @@ -92,8 +80,8 @@ class MdocPostProcessor(implicit ctx: Context) extends DocumentPostProcessor { } } - def processStringInput(doc: Document, custom: StringBlockInput): Unit = { - val StringBlockInput(block, input, Str(mod, info)) = custom + def processStringInput(doc: MarkdownFile, custom: StringFenceInput): Unit = { + val StringFenceInput(block, input, Str(mod, info)) = custom try { val newText = mod.process(info, input, ctx.reporter) replaceNodeWithText(doc, block, newText) @@ -107,13 +95,13 @@ class MdocPostProcessor(implicit ctx: Context) extends DocumentPostProcessor { } def processScalaInputs( - doc: Document, - inputs: List[ScalaBlockInput], + doc: MarkdownFile, + inputs: List[ScalaFenceInput], inputFile: RelativePath, filename: String ): Unit = { val sectionInputs = inputs.map { - case ScalaBlockInput(_, input, mod) => + case ScalaFenceInput(_, input, mod) => import scala.meta._ dialects.Sbt1(input).parse[Source] match { case parsers.Parsed.Success(source) => @@ -136,8 +124,8 @@ class MdocPostProcessor(implicit ctx: Context) extends DocumentPostProcessor { filename ) rendered.sections.zip(inputs).foreach { - case (section, ScalaBlockInput(block, _, mod)) => - block.setInfo(CharSubSequence.of("scala")) + case (section, ScalaFenceInput(block, _, mod)) => + block.newInfo = Some("scala") def defaultRender: String = Renderer.renderEvaluatedSection( rendered, section, @@ -188,7 +176,7 @@ class MdocPostProcessor(implicit ctx: Context) extends DocumentPostProcessor { } else if (m.isSilent) { () // Do nothing } else { - block.setContent(List[BasedSequence](CharSubSequence.of(defaultRender)).asJava) + block.newBody = Some(defaultRender) } case c: Modifier.Str => throw new IllegalArgumentException(c.toString) @@ -212,55 +200,35 @@ class MdocPostProcessor(implicit ctx: Context) extends DocumentPostProcessor { } } - def asBasedSequence(string: String): util.List[BasedSequence] = { - List[BasedSequence](CharSubSequence.of(string)).asJava - } - - def appendChild(doc: Document, text: String): Unit = { + def appendChild(doc: MarkdownFile, text: String): Unit = { if (text.nonEmpty) { - doc.appendChild(parseMarkdown(doc, text)) + doc.appendText(text) } } - def parseMarkdown(doc: Document, text: String): Node = { - val markdownOptions = new MutableDataSet() - markdownOptions.setAll(doc) - Markdown.parse(CharSubSequence.of(text), markdownOptions) - } - - def replaceNodeWithText(doc: Document, toReplace: Node, text: String): Unit = { - val child = parseMarkdown(doc, text) - toReplace.insertAfter(child) - toReplace.unlink() + def replaceNodeWithText(doc: MarkdownFile, toReplace: CodeFence, text: String): Unit = { + toReplace.newPart = Some(text) } - def collectBlockInputs( - doc: Document, - docInput: Input - ): (List[ScalaBlockInput], List[StringBlockInput], List[PreBlockInput]) = { - val InterestingCodeFence = new BlockInput(ctx, docInput) - val inputs = List.newBuilder[ScalaBlockInput] - val strings = List.newBuilder[StringBlockInput] - val pres = List.newBuilder[PreBlockInput] - Markdown.traverse[FencedCodeBlock](doc) { + def collectFenceInputs( + doc: MarkdownFile + ): (List[ScalaFenceInput], List[StringFenceInput], List[PreFenceInput]) = { + val InterestingCodeFence = new FenceInput(ctx, doc.input) + val inputs = List.newBuilder[ScalaFenceInput] + val strings = List.newBuilder[StringFenceInput] + val pres = List.newBuilder[PreFenceInput] + doc.parts.foreach { case InterestingCodeFence(input) => input.mod match { case string: Str => - strings += StringBlockInput(input.block, input.input, string) + strings += StringFenceInput(input.block, input.input, string) case pre: Pre => - pres += PreBlockInput(input.block, input.input, pre) + pres += PreFenceInput(input.block, input.input, pre) case _ => inputs += input } + case _ => } (inputs.result(), strings.result(), pres.result()) } } - -object MdocPostProcessor { - class Factory(context: Context) extends DocumentPostProcessorFactory { - override def create(document: ast.Document): DocumentPostProcessor = { - new MdocPostProcessor()(context) - } - } -} diff --git a/tests/unit/src/test/scala-2.12/tests/markdown/JsSuite.scala b/tests/unit/src/test/scala-2.12/tests/markdown/JsSuite.scala index fa365631..c5ace36d 100644 --- a/tests/unit/src/test/scala-2.12/tests/markdown/JsSuite.scala +++ b/tests/unit/src/test/scala-2.12/tests/markdown/JsSuite.scala @@ -15,7 +15,6 @@ class JsSuite extends BaseMarkdownSuite { def suffix(name: String): String = s"""| - | | |""".stripMargin @@ -25,15 +24,12 @@ class JsSuite extends BaseMarkdownSuite { |```scala mdoc:js |println("hello world!") |``` - """.stripMargin, + |""".stripMargin, """|```scala |println("hello world!") |``` - | |
- | | - | | """.stripMargin ) @@ -64,21 +60,17 @@ class JsSuite extends BaseMarkdownSuite { |```scala mdoc:js |println("hello 2!") |``` - """.stripMargin, + |""".stripMargin, """|```scala |println("hello 1!") |``` - | |
| |```scala |println("hello 2!") |``` - | |
- | | - | | |""".stripMargin ) @@ -143,7 +135,7 @@ class JsSuite extends BaseMarkdownSuite { |```scala mdoc:js |println(x) |``` - """.stripMargin, + |""".stripMargin, """|```scala |val x = 1 |``` @@ -151,11 +143,8 @@ class JsSuite extends BaseMarkdownSuite { |```scala |println(x) |``` - | |
- | | - | | """.stripMargin ) @@ -199,7 +188,7 @@ class JsSuite extends BaseMarkdownSuite { | | c | '''.stripMargin |``` - """.stripMargin.triplequoted, + |""".stripMargin.triplequoted, s"""|```scala |val x = ''' | |a @@ -207,9 +196,7 @@ class JsSuite extends BaseMarkdownSuite { | | c | '''.stripMargin |``` - | |
- | |${suffix("stripMargin")} |""".stripMargin.triplequoted ) @@ -220,10 +207,9 @@ class JsSuite extends BaseMarkdownSuite { |```scala mdoc:js:invisible |println("Hello!") |``` - """.stripMargin, + |""".stripMargin, s"""| |
- | |${suffix("invisible")} |""".stripMargin ) @@ -263,7 +249,7 @@ class JsSuite extends BaseMarkdownSuite { |```scala mdoc:js:shared:not |println(1) |``` - """.stripMargin, + |""".stripMargin, """|error: mods-error.md:2:25: invalid modifier 'not' |```scala mdoc:js:shared:not | ^^^ @@ -276,19 +262,14 @@ class JsSuite extends BaseMarkdownSuite { |```scala mdoc:js |println("Hello!") |``` - """.stripMargin, + |""".stripMargin, """|```scala |println("Hello!") |``` - | |
- | | - | | - | | - | | |""".stripMargin, settings = { @@ -314,14 +295,11 @@ class JsSuite extends BaseMarkdownSuite { |```scala mdoc:js:invisible |println("Hello!") |``` - """.stripMargin, + |""".stripMargin, s"""| |
- | |$unpkgReact - | | - | | |""".stripMargin, settings = { diff --git a/tests/unit/src/test/scala/tests/cli/CliSuite.scala b/tests/unit/src/test/scala/tests/cli/CliSuite.scala index 2cdd1a41..4eca3a38 100644 --- a/tests/unit/src/test/scala/tests/cli/CliSuite.scala +++ b/tests/unit/src/test/scala/tests/cli/CliSuite.scala @@ -137,7 +137,6 @@ class CliSuite extends BaseCliSuite { """ |/before-index.md |# Header - | |[Head](#head) """.stripMargin, // did not generate index.md expectedExitCode = 1, diff --git a/tests/unit/src/test/scala/tests/cli/ScalacOptionsSuite.scala b/tests/unit/src/test/scala/tests/cli/ScalacOptionsSuite.scala index 4ea22011..8cb84cd3 100644 --- a/tests/unit/src/test/scala/tests/cli/ScalacOptionsSuite.scala +++ b/tests/unit/src/test/scala/tests/cli/ScalacOptionsSuite.scala @@ -87,7 +87,6 @@ class ScalacOptionsSuite extends BaseCliSuite { |println("1") |// 1 |``` - | |```scala |println("2") |// 2 diff --git a/tests/unit/src/test/scala/tests/markdown/BaseMarkdownSuite.scala b/tests/unit/src/test/scala/tests/markdown/BaseMarkdownSuite.scala index 21db1c8a..ab20dfe7 100644 --- a/tests/unit/src/test/scala/tests/markdown/BaseMarkdownSuite.scala +++ b/tests/unit/src/test/scala/tests/markdown/BaseMarkdownSuite.scala @@ -17,6 +17,7 @@ import scala.meta.io.AbsolutePath import scala.meta.testkit.DiffAssertions import tests.markdown.StringSyntax._ import mdoc.internal.pos.PositionSyntax._ +import scala.meta.io.RelativePath abstract class BaseMarkdownSuite extends org.scalatest.FunSuite with DiffAssertions { def createTempDirectory(): AbsolutePath = { @@ -43,6 +44,7 @@ abstract class BaseMarkdownSuite extends org.scalatest.FunSuite with DiffAsserti def postProcessExpected: Map[String, String => String] = Map.empty private val myStdout = new ByteArrayOutputStream() private def newReporter(): ConsoleReporter = { + myStdout.reset() new ConsoleReporter(new PrintStream(myStdout)) } protected def scalacOptions: String = "" @@ -53,11 +55,6 @@ abstract class BaseMarkdownSuite extends org.scalatest.FunSuite with DiffAsserti Context(settings, reporter, compiler) } - def getMarkdownSettings(context: Context): MutableDataSet = { - myStdout.reset() - Markdown.mdocSettings(context) - } - def checkError( name: String, original: String, @@ -69,7 +66,8 @@ abstract class BaseMarkdownSuite extends org.scalatest.FunSuite with DiffAsserti val reporter = newReporter() val context = newContext(settings, reporter) val input = Input.VirtualFile(name + ".md", original) - Markdown.toMarkdown(input, getMarkdownSettings(context), reporter, settings) + val relpath = RelativePath(input.path) + Markdown.toMarkdown(input, context, relpath, baseSettings.site, reporter, settings) assert(reporter.hasErrors, "Expected errors but reporter.hasErrors=false") val obtainedErrors = Compat.postProcess( fansi.Str(myStdout.toString).plainText.trimLineEnds, @@ -89,8 +87,9 @@ abstract class BaseMarkdownSuite extends org.scalatest.FunSuite with DiffAsserti val reporter = newReporter() val context = newContext(settings, reporter) val input = Input.VirtualFile(name + ".md", original) + val relpath = RelativePath(input.path) val obtained = - Markdown.toMarkdown(input, getMarkdownSettings(context), reporter, settings).trimLineEnds + Markdown.toMarkdown(input, context, relpath, baseSettings.site, reporter, settings) val colorOut = myStdout.toString() print(colorOut) val stdout = fansi.Str(colorOut).plainText diff --git a/tests/unit/src/test/scala/tests/markdown/CompileOnlySuite.scala b/tests/unit/src/test/scala/tests/markdown/CompileOnlySuite.scala index f9cda158..1e76d8f1 100644 --- a/tests/unit/src/test/scala/tests/markdown/CompileOnlySuite.scala +++ b/tests/unit/src/test/scala/tests/markdown/CompileOnlySuite.scala @@ -33,12 +33,10 @@ class CompileOnlySuite extends BaseMarkdownSuite { |val message = "Enter: " |// message: String = "Enter: " |``` - | |```scala |println(message) |println(System.in.read()) |``` - | |```scala |println(message) |// Enter: diff --git a/tests/unit/src/test/scala/tests/markdown/FailSuite.scala b/tests/unit/src/test/scala/tests/markdown/FailSuite.scala index 5cd898a3..56c68cf9 100644 --- a/tests/unit/src/test/scala/tests/markdown/FailSuite.scala +++ b/tests/unit/src/test/scala/tests/markdown/FailSuite.scala @@ -106,7 +106,6 @@ class FailSuite extends BaseMarkdownSuite { |println(42) |// 42 |``` - | |```scala |val x: Int = "String" |// error: type mismatch; diff --git a/tests/unit/src/test/scala/tests/markdown/MarkdownFileSuite.scala b/tests/unit/src/test/scala/tests/markdown/MarkdownFileSuite.scala new file mode 100644 index 00000000..a726fd56 --- /dev/null +++ b/tests/unit/src/test/scala/tests/markdown/MarkdownFileSuite.scala @@ -0,0 +1,110 @@ +package tests.markdown + +import org.scalatest.FunSuite +import scala.meta.testkit.DiffAssertions +import mdoc.internal.markdown.MarkdownFile +import scala.meta.inputs.Input +import mdoc.internal.io.ConsoleReporter +import mdoc.internal.markdown.Text +import mdoc.internal.markdown.MarkdownPart +import mdoc.internal.markdown.CodeFence +import scala.meta.io.RelativePath + +class MarkdownFileSuite extends FunSuite with DiffAssertions { + val reporter = new ConsoleReporter(System.out) + + def check(name: String, original: String, expected: MarkdownPart*): Unit = { + test(name) { + reporter.reset() + val input = Input.VirtualFile(name, original) + val relpath = RelativePath(input.path) + val obtained = MarkdownFile.parse(input, relpath, reporter).parts + require(!reporter.hasErrors) + val expectedParts = expected.toList + assertNoDiff( + pprint.tokenize(obtained).mkString, + pprint.tokenize(expectedParts).mkString + ) + } + } + + check( + "basic", + """# Hello + |World + |```scala mdoc + |println(42) + |``` + |End. + |""".stripMargin, + Text("# Hello\n"), + Text("World\n"), + CodeFence( + Text("```"), + Text("scala mdoc\n"), + Text("println(42)"), + Text("\n```\n") + ), + Text("End.\n") + ) + + check( + "four-backtick", + """# Hello + |World + |````scala mdoc + |``` + |println(42) + |``` + |```` + |End. + |""".stripMargin, + Text("# Hello\n"), + Text("World\n"), + CodeFence( + Text("````"), + Text("scala mdoc\n"), + Text("```\nprintln(42)\n```"), + Text("\n````\n") + ), + Text("End.\n") + ) + + check( + "two-backtick", + """# Hello + |World + |``scala mdoc + |println(42) + |`` + |End. + |""".stripMargin, + Text("# Hello\n"), + Text("World\n"), + Text("``scala mdoc\n"), + Text("println(42)\n"), + Text("``\n"), + Text("End.\n") + ) + + check( + "backtick-mismatch", + """|````scala mdoc + |````` + |```` + |42 + |""".stripMargin, + CodeFence( + Text("````"), + Text("scala mdoc\n"), + Text(""), + Text("\n`````\n") + ), + CodeFence( + Text("````"), + Text("\n"), + Text("42"), + Text("\n") + ) + ) +} diff --git a/tests/unit/src/test/scala/tests/markdown/MultiModsSuite.scala b/tests/unit/src/test/scala/tests/markdown/MultiModsSuite.scala index 81a1c06c..5f62b2fd 100644 --- a/tests/unit/src/test/scala/tests/markdown/MultiModsSuite.scala +++ b/tests/unit/src/test/scala/tests/markdown/MultiModsSuite.scala @@ -12,8 +12,9 @@ class MultiModsSuite extends BaseMarkdownSuite { |```scala mdoc:reset:fail |println(x) |``` - """.stripMargin, - """|```scala + |""".stripMargin, + """| + |```scala |val x = 1 |// x: Int = 1 |``` @@ -24,7 +25,7 @@ class MultiModsSuite extends BaseMarkdownSuite { |// println(x) |// ^ |``` - """.stripMargin + |""".stripMargin ) check( @@ -69,6 +70,7 @@ class MultiModsSuite extends BaseMarkdownSuite { |// x: Int = 1 |``` | + | |```scala |println(x) |// 2 diff --git a/tests/unit/src/test/scala/tests/markdown/ResetSuite.scala b/tests/unit/src/test/scala/tests/markdown/ResetSuite.scala index f6ce178a..b9bce5ed 100644 --- a/tests/unit/src/test/scala/tests/markdown/ResetSuite.scala +++ b/tests/unit/src/test/scala/tests/markdown/ResetSuite.scala @@ -50,6 +50,7 @@ class ResetSuite extends BaseMarkdownSuite { |``` | |```scala mdoc + |implicit val x: Int = 41 |println(x) |``` """.stripMargin, @@ -58,12 +59,15 @@ class ResetSuite extends BaseMarkdownSuite { |// x: Int = 42 |``` | - |```scala mdoc:reset + |```scala + | |``` | |```scala + |implicit val x: Int = 41 + |// x: Int = 41 |println(x) - |// 42 + |// 41 |``` """.stripMargin ) diff --git a/tests/unit/src/test/scala/tests/markdown/ScastieModifierSuite.scala b/tests/unit/src/test/scala/tests/markdown/ScastieModifierSuite.scala index 18a2247a..463d9ec8 100644 --- a/tests/unit/src/test/scala/tests/markdown/ScastieModifierSuite.scala +++ b/tests/unit/src/test/scala/tests/markdown/ScastieModifierSuite.scala @@ -34,17 +34,15 @@ class ScastieModifierSuite extends BaseMarkdownSuite { """.stripMargin, s""" | - | |

-       |
        |
     """.stripMargin
   )
@@ -58,17 +56,15 @@ class ScastieModifierSuite extends BaseMarkdownSuite {
     """.stripMargin,
     s"""
        |
-       |
        |

-       |
        |
     """.stripMargin,
     settings = darkThemeSettings
diff --git a/tests/unit/src/test/scala/tests/markdown/SilentSuite.scala b/tests/unit/src/test/scala/tests/markdown/SilentSuite.scala
index 0935e025..56e40f24 100644
--- a/tests/unit/src/test/scala/tests/markdown/SilentSuite.scala
+++ b/tests/unit/src/test/scala/tests/markdown/SilentSuite.scala
@@ -8,6 +8,7 @@ class SilentSuite extends BaseMarkdownSuite {
       |```scala mdoc:silent
       |val x = 4
       |```
+      |
       |```scala mdoc
       |println(x)
       |```
diff --git a/tests/unit/src/test/scala/tests/markdown/SiteVariableInjectorSuite.scala b/tests/unit/src/test/scala/tests/markdown/SiteVariableInjectorSuite.scala
index 321fa548..513cb20b 100644
--- a/tests/unit/src/test/scala/tests/markdown/SiteVariableInjectorSuite.scala
+++ b/tests/unit/src/test/scala/tests/markdown/SiteVariableInjectorSuite.scala
@@ -7,7 +7,7 @@ class SiteVariableInjectorSuite extends BaseMarkdownSuite {
       |# Hey ![version]
     """.stripMargin,
     """
-      |# Hey 1.0
+      |# Hey ![version]
     """.stripMargin
   )
 
@@ -17,7 +17,7 @@ class SiteVariableInjectorSuite extends BaseMarkdownSuite {
       |I am ![version]
     """.stripMargin,
     """
-      |I am 1.0
+      |I am ![version]
     """.stripMargin
   )
 
@@ -32,7 +32,7 @@ class SiteVariableInjectorSuite extends BaseMarkdownSuite {
       |
       || C1 | C2 |
       || == | == |
-      || 1.0 | hello |
+      || ![version] | hello |
     """.stripMargin
   )
 
diff --git a/tests/unit/src/test/scala/tests/markdown/ToStringSuite.scala b/tests/unit/src/test/scala/tests/markdown/ToStringSuite.scala
index 6e0369e3..84971066 100644
--- a/tests/unit/src/test/scala/tests/markdown/ToStringSuite.scala
+++ b/tests/unit/src/test/scala/tests/markdown/ToStringSuite.scala
@@ -16,7 +16,6 @@ class ToStringSuite extends BaseMarkdownSuite {
        |List("a")
        |// res0: List[String] = List(a)
        |```
-       |
        |```scala
        |List("a")
        |// res1: List[String] = List("a")