Scala mdoc: Verified Code in Markdown


mdoc is a Markdown preprocessor for Scala that makes code examples honest. Instead of manually copying compiler output into documentation, mdoc compiles and runs the snippets for you — then inlines the results.

How it works

Given a Markdown file with a standard Scala code block:

```scala
   case class Point2d(x: Int, y: Int)
   val p = Point2d(10, 20)
``

Replacing scala with scala mdoc hands the snippet to the processing engine:

```scala mdoc
   case class Point2d(x: Int, y: Int)
   val p = Point2d(10, 20)
``

mdoc compiles the snippet, runs it, and appends the compiler output inline:

case class Point2d(x: Int, y: Int)
val p = Point2d(10, 20)
// p: Point2d = Point2d(x = 10, y = 20)

Compile errors

The mdoc:fail modifier captures compiler errors instead of failing silently:

```scala mdoc:fail
sealed trait Color
case object Red extends Color
case object Yellow extends Color
case object Green extends Color

def show(c: Color): String = c match {
  case Red => "Red"
  case Green => "Green"
}
``

becomes

sealed trait Color
case object Red extends Color
case object Yellow extends Color
case object Green extends Color

def show(c: Color): String = c match {
  case Red => "Red"
  case Green => "Green"
}
// error: match may not be exhaustive.
// It would fail on the following input: Yellow
// Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=other-match-analysis, site=repl.MdocSession.MdocApp.show
// def show(c: Color): String = c match {
//                              ^

Runtime exceptions

The mdoc:crash modifier captures exceptions in the same way:

```scala mdoc:crash
println(1/0)
``

becomes

println(1/0)
// java.lang.ArithmeticException: / by zero
// 	at repl.MdocSession$MdocApp$$anonfun$1.apply$mcV$sp(2024-03-15-why-scala.md:33)
// 	at repl.MdocSession$MdocApp$$anonfun$1.apply(2024-03-15-why-scala.md:33)
// 	at repl.MdocSession$MdocApp$$anonfun$1.apply(2024-03-15-why-scala.md:33)

sbt integration

The sbt-mdoc plugin integrates mdoc into the build, making it part of CI/CD. Configuration is minimal.

Add the plugin to plugins.sbt:

  addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.5.2")

Add a docs subproject to build.sbt:

  lazy val `xyz-docs` = (project in file("xyz-docs"))
    .enablePlugins(MdocPlugin)

Place .md source files in the docs folder, then from the sbt shell:

xyz-docs/mdoc

Processed files land in xyz-docs/target/mdoc.

mdoc also supports custom processors — you can inject arbitrary content into your Markdown files as part of the pipeline.