mdoc

mdoc

  • Docs
  • Blog
  • GitHub

›Modifiers

Overview

  • Installation
  • Why mdoc?
  • Coming from tut
  • Changelog

Modifiers

  • JVM
  • Scala.js

Site generators

  • Docusaurus
Edit

JVM Modifiers

Code fences with the scala mdoc modifier are compiled and evaluated on the JVM at markdown generation time.

Default

The default modifier compiles and executes the code fence as normal

Before:

```scala mdoc
val x = 1
val y = 2
x + y
```

After:

```scala
val x = 1
// x: Int = 1
val y = 2
// y: Int = 2
x + y
// res0: Int = 3
```

silent

The silent modifier is identical to the default modifier except that it hides the evaluated output. The input code fence renders unchanged.

Before:

```scala mdoc:silent
val x = 1
val y = 2
x + y
```
```scala mdoc
x + y
```

After:

```scala
val x = 1
val y = 2
x + y
```
```scala
x + y
// res1: Int = 3
```

fail

The fail modifier asserts that the code block will not compile. The rendered output contains the type error message.

Before:

```scala mdoc:fail
val x: Int = ""
```

After:

```scala
val x: Int = ""
// error: type mismatch;
//  found   : String("")
//  required: Int
// val x: Int = ""
//              ^^
```

A fail code fence with no compile error fails the build.

Before:

```scala mdoc:fail
val x: String = ""
```

Error:

error: modifiers.md:2:1: Expected compile errors but program compiled successfully without errors
val x: String = ""
^^^^^^^^^^^^^^^^^^

Note that fail does not assert that the program compiles but crashes at runtime. To assert runtime exceptions, use the crash modifier.

warn

The warn modifier is similar to fail except that it asserts the code compiles successfully but with a warning message.

Before:

```scala mdoc:warn
List(1) match {
  case Nil =>
}
```

After:

```scala
List(1) match {
  case Nil =>
}
// warning: match may not be exhaustive.
// It would fail on the following input: List(_)
// List(1) match {
// ^^^^^^^
```

The build fails when a warn code fence compiles without warnings.

Before:

```scala mdoc:warn
List(1) match {
  case head :: tail =>
  case Nil =>
}
```

Error:

error: modifiers.md:2:1: Expected compile warnings but program compiled successfully without warnings
List(1) match {
^

The build also fails when a warn code fence fails to compile, even if the program has a warning. Use fail in these cases instead.

Before:

```scala mdoc:warn
val x: Int = ""
```

Error:

error: modifiers.md:2:1: Expected compile warnings but program failed to compile
val x: Int = ""
^^^^^^^^^^^^^^^

crash

The crash modifier asserts that the code block throws an exception at runtime

Before:

```scala mdoc:crash
val y = ???
```

After:

```scala
val y = ???
// scala.NotImplementedError: an implementation is missing
//  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:288)
//  at repl.MdocSession$MdocApp$$anonfun$1.apply$mcV$sp(modifiers.md:9)
//  at repl.MdocSession$MdocApp$$anonfun$1.apply(modifiers.md:8)
//  at repl.MdocSession$MdocApp$$anonfun$1.apply(modifiers.md:8)
```

passthrough

The passthrough modifier collects the stdout and stderr output from the program and embeds it verbatim in the markdown file.

Before:

```scala mdoc:passthrough
val matrix = Array.tabulate(4, 4) { (a, b) =>
  val multiplied = (a + 1) * (b + 1)
  f"$multiplied%2s"
}
val table = matrix.map(_.mkString("| ", " | ", " |")).mkString("\n")
println(s"""
This will be rendered as markdown.

* Bullet 1
* Bullet 2

Look at the table:

$table
""")
```

After:

This will be rendered as markdown.

* Bullet 1
* Bullet 2

Look at the table:

|  1 |  2 |  3 |  4 |
|  2 |  4 |  6 |  8 |
|  3 |  6 |  9 | 12 |
|  4 |  8 | 12 | 16 |

invisible

The invisible modifier evaluates the code but does not render anything. The invisible modifier is equivalent to passthrough when the expression does not print to stdout.

Before:

This is prose.
```scala mdoc:invisible
println("I am invisible")
```
More prose.

After:

This is prose.
More prose.

reset

The reset modifier starts a new scope where previous statements in the document are no longer available. This can be helpful to clear existing imports or implicits in scope.

Before:

```scala mdoc
implicit val x: Int = 41
```

```scala mdoc:reset
implicit val y: Int = 42
implicitly[Int] // x is no longer in scope
```
```scala mdoc:fail
println(x)
```

After:

```scala
implicit val x: Int = 41
// x: Int = 41
```

```scala
implicit val y: Int = 42
// y: Int = 42
implicitly[Int] // x is no longer in scope
// res1: Int = 42
```
```scala
println(x)
// error: not found: value x
// println(x)
//         ^
```

reset-class

This modifier works exactly the same way as :reset and is deprecated. Please use :reset instead.

reset-object

The :reset-object modifier is similar to :reset except that it wraps the following statements in a object instead of a class. By default, statements are wrapped in an class but that encoding can cause problems compile errors such as

  • "Value class may not be a member of another class": happens when using extends AnyVal
  • "The outer reference in this type test cannot be checked at run time": happens when pattern matching on sealed ADT.

Example case where the default class wrapping does not compile:

Before:

```scala mdoc
final case class Name(val value: String) extends AnyVal
```

Error:

error: modifiers.md:2:1: value class may not be a member of another class
final case class Name(val value: String) extends AnyVal
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Use :reset-object to avoid these compile errors

Before:

```scala mdoc:reset-object
final case class Name(val value: String) extends AnyVal
```

After:

```scala
final case class Name(val value: String) extends AnyVal
```

nest

The nest modifier starts a new scope similar to reset but previous variables in the scope are still accessible. This can be helpful to redefine variable names while still being able to access existing variables in scope.

Before:

```scala mdoc
val x = 40
val y = 1
```

```scala mdoc:nest
val x: Int = 41
println(x + y)
```

After:

```scala
val x = 40
// x: Int = 40
val y = 1
// y: Int = 1
```

```scala
val x: Int = 41
// x: Int = 41
println(x + y)
// 42
```

Without nest in the example above, the document would fail to compile because the variable x was already defined in scope.

A nest block wraps subsequent code blocks inside _root_.scala.Predef.locally{...}. This encoding means that it's not possible to define value classes inside a nested block.

Before:

```scala mdoc
val x: Int = 41
```

```scala mdoc:nest
case class Foo(val y: Int) extends AnyVal
Foo(x)
```

Error:

error: modifiers.md:6:1: value class may not be a local class
case class Foo(val y: Int) extends AnyVal
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Also, nested scopes do not ambiguate between conflicting implicits.

Before:

```scala mdoc
implicit val x: Int = 41
```

```scala mdoc:nest
implicit val y: Int = 41
implicitly[Int]
```

Error:

error: modifiers.md:7:1: ambiguous implicit values:
 both value x in class MdocApp of type => Int
 and value y of type Int
 match expected type Int
implicitly[Int]
^^^^^^^^^^^^^^^

A workaround for ambiguous implicits in nested scopes is to shadow one implicit by redefining its variable name.

Before:

```scala mdoc
implicit val shadowMe: Int = 41
```

```scala mdoc:nest
implicit val shadowMe: Int = 42
implicitly[Int]
```

After:

```scala
implicit val shadowMe: Int = 41
// shadowMe: Int = 41
```

```scala
implicit val shadowMe: Int = 42
// shadowMe: Int = 42
implicitly[Int]
// res0: Int = 42
```

to-string

The toString modifier changes the pretty-printer for runtime values to use Object.toString instead of PPrint.

Before:

```scala mdoc:to-string
List("no quotes")
```
```scala mdoc
List("with quotes")
```

After:

```scala
List("no quotes")
// res0: List[String] = List(no quotes)
```
```scala
List("with quotes")
// res1: List[String] = List("with quotes")
```

width

The width=NUMBER allows you to override max width (deault is 80) of pretty-printed values.

Before:

```scala mdoc:width=20
List.fill(2)(List(1,2,3,4,5))
```
```scala mdoc
List.fill(2)(List(1,2,3,4,5))
```

After:

```scala
List.fill(2)(List(1,2,3,4,5))
// res0: List[List[Int]] = List(
//   List(
//     1,
//     2,
//     3,
//     4,
//     5
//   ),
//   List(
//     1,
//     2,
//     3,
//     4,
//     5
//   )
// )
```
```scala
List.fill(2)(List(1,2,3,4,5))
// res1: List[List[Int]] = List(List(1, 2, 3, 4, 5), List(1, 2, 3, 4, 5))
```

height=

The height=NUMBER allows you to override max height (default is 50) of pretty-printed values.

Before:

```scala mdoc:height=5
List.fill(15)("hello world!")
```
```scala mdoc
List.fill(15)("hello world!")
```

After:

```scala
List.fill(15)("hello world!")
// res0: List[String] = List(
//   "hello world!",
//   "hello world!",
//   "hello world!",
// ...
```
```scala
List.fill(15)("hello world!")
// res1: List[String] = List(
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!",
//   "hello world!"
// )
```

compile-only

The compile-only modifier ensures the code example compiles without evaluating the program at runtime. This can be helpful to demonstrate code examples that perform side-effects.

Before:

```scala mdoc:compile-only
val name = scala.io.StdIn.readLine("Enter your name: ")
```

After:

```scala
val name = scala.io.StdIn.readLine("Enter your name: ")
```

scastie

The scastie modifier transforms a Scala code block into a Scastie snippet.

ℹ️ This modifier will work only in environments that support embedding a <script> tag. For example, it won't work in GitHub readmes, but it will work when building a static website from Markdown (e.g., with Docusaurus)

You can embed an existing Scastie snippet by its id:

Before:

```scala mdoc:scastie:xbrvky6fTjysG32zK6kzRQ

```

After:

<script src='https://scastie.scala-lang.org/xbrvky6fTjysG32zK6kzRQ.js?theme=light'></script>

or in case of a user's snippet:

Before:

```scala mdoc:scastie:MasseGuillaume/CpO2s8v2Q1qGdO3vROYjfg

```

After:

<script src='https://scastie.scala-lang.org/MasseGuillaume/CpO2s8v2Q1qGdO3vROYjfg.js?theme=light'></script>

⚠️ The empty line in the block can't be omitted due to how the Markdown parser works

Moreover, you can quickly translate any Scala code block block into a Scastie snippet on the fly.

Before:

```scala mdoc:scastie
val x = 1 + 2
println(x)
```

After:

<script src="https://scastie.scala-lang.org/embedded.js"></script>
<pre class='scastie-snippet-e4b077aa-9a50-44e5-bd8e-d3ffdf62b767'></pre>
<script>window.addEventListener('load', function() {
 scastie.Embedded('.scastie-snippet-e4b077aa-9a50-44e5-bd8e-d3ffdf62b767', {
   code: `val x = 1 + 2
println(x)`,
   theme: 'light',
    isWorksheetMode: true,
    targetType: 'jvm',
    scalaVersion: '2.12.6'
  })
})</script>

⚠️ Inline snippets are slower to run than embedded ones, since they won't be cached. You should prefer embedding existing snippets whenever possible.

You can choose the Scastie theme when initializing the Scastie modifier:

import mdoc.modifiers.ScastieModifier
new ScastieModifier(theme = "dark") // default is "light"
// res0: ScastieModifier = StringModifier(mdoc:scastie)

PostModifier

A PostModifier is a custom modifier that post-processes a compiled and interpreted mdoc code fence. Post modifiers have access to the original code fence text, the static types and runtime values of the evaluated Scala code, the input and output file paths and other contextual information.

One example use-case for post modifiers is to render charts based on the runtime value of the last expression in the code fence.

Extend the mdoc.PostModifier trait to implement a post modifier.

File: EvilplotModifier.scala

package mdoc.docs

import com.cibo.evilplot.geometry.Drawable
import java.nio.file.Files
import java.nio.file.Paths
import mdoc._
import scala.meta.inputs.Position

class EvilplotModifier extends PostModifier {
  val name = "evilplot"
  def process(ctx: PostModifierContext): String = {
    val relpath = Paths.get(ctx.info)
    val out = ctx.outputFile.toNIO.getParent.resolve(relpath)
    ctx.lastValue match {
      case d: Drawable =>
        Files.createDirectories(out.getParent)
        if (!Files.isRegularFile(out)) {
          d.write(out.toFile)
        }
        s"![](${ctx.info})"
      case _ =>
        val (pos, obtained) = ctx.variables.lastOption match {
          case Some(variable) =>
            val prettyObtained =
              s"${variable.staticType} = ${variable.runtimeValue}"
            (variable.pos, prettyObtained)
          case None =>
            (Position.Range(ctx.originalCode, 0, 0), "nothing")
        }
        ctx.reporter.error(
          pos,
          s"""type mismatch:
  expected: com.cibo.evilplot.geometry.Drawable
  obtained: $obtained"""
        )
        ""
    }
  }
}

Next, create a resource file META-INF/services/mdoc.PostModifier so the post modififer is recognized by the JVM ServiceLoader framework.

File: mdoc.PostModifier

mdoc.docs.EvilplotModifier

As long as EvilplotModifier is available on the classpath, for example via libraryDependencies in build.sbt, then you can use the modifier like this.

Before:

```scala mdoc:evilplot:assets/scatterplot.png
import com.cibo.evilplot._
import com.cibo.evilplot.plot._
import com.cibo.evilplot.plot.aesthetics.DefaultTheme._
import com.cibo.evilplot.numeric.Point

val data = Seq.tabulate(90) { i =>
  val degree = i * 8
  val radian = math.toRadians(degree)
  Point(i.toDouble, math.sin(radian))
}

ScatterPlot(data)
  .xAxis()
  .yAxis()
  .frame()
  .xLabel("x")
  .yLabel("y")
  .render()
```

After:

![](assets/scatterplot.png)

Which renders into a scatter plot like this:

It's important that post modifiers present helpful error messages to the user in case of failures. For example, if the last runtime value is not an EvilPlot Drawable we can report the expected and obtained types with carets pointing to the position of the last variable.

Before:

```scala mdoc:evilplot:scatterplot.png
val message = "hello world!"
```

Error:

error: modifiers.md:2:5: type mismatch:
  expected: com.cibo.evilplot.geometry.Drawable
  obtained: String = hello world!
val message = "hello world!"
    ^^^^^^^

StringModifier

A StringModifier is a custom modifier that processes the plain text contents of a code block, ignoring the compilation and interpretation of the Scala code.

import mdoc.StringModifier
import mdoc.Reporter
import scala.meta.Input
class FooModifier extends StringModifier {
  override val name = "foo"
  override def process(info: String, code: Input, reporter: Reporter): String = {
    val originalCodeFenceText = code.text
    val isCrash = info == "crash"
    if (isCrash) "BOOM"
    else "OK: " + originalCodeFenceText
  }
}

Next, create a resource file META-INF/services/mdoc.StringModifier so the post modififer is recognized by the JVM ServiceLoader framework.

File: mdoc.StringModifier

mdoc.docs.FooModifier
mdoc.docs.SbtModifier
mdoc.docs.FileModifier

Code blocks with the mdoc:foo modifier will render as follows.

Before:

```scala mdoc:foo
Hello world!
```

After:

OK: Hello world!

We can also add the argument :crash to render "BOOM".

Before:

```scala mdoc:foo:crash
Hello world!
```

After:

BOOM
← ChangelogScala.js →
  • Default
  • silent
  • fail
  • warn
  • crash
  • passthrough
  • invisible
  • reset
  • reset-class
  • reset-object
  • nest
  • to-string
  • width
  • height=
  • compile-only
  • scastie
  • PostModifier
  • StringModifier
mdoc
Docs
Get started
Community
Chat on Gitter
More
GitHub
Copyright © 2025 mdoc developers