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.