Snippet validation in Scaladoc
This feature is still under development and it does not target 3.0.0 but rather 3.0.1 or later.
The main functionality of project documentation is to help people understand and use the project properly. Sometimes a part of a project needs few words to show its usage, but every developer knows that there are moments where description is not enough and nothing is better than a good ol’ example. The convenient way of providing examples in documentation is to create code snippets presenting usage of given functionality. The problem with code snippets is that simultaneously with project development, they need to be updated. Sometimes changes in one part of a project may break examples in other parts. The number of snippets and the amount of time passed since they’ve been written makes it impossible to remember every place where you need to fix them. After some time you realize that your documentation is a complete mess and you need to go through all examples and rewrite them. Many Scala 2 projects use typechecked markdown documentation with tut or mdoc. Almost everyone at least heard about these tools. As they turned out to be very useful and the Scala community successfully adopted them, we’re planning to incorporate the features of tut and mdoc into the compiler so that it’s included out of the box with Scaladoc.
Context
Our goal is to make snippets behave as much as possible as it is defined within a given member. For us it brings a natural feel to snippets. To achieve this, we implemented a wrapping mechanism that provides a context for each snippet.
Let’s assume that for some method foo
inside class Bar
in package baz
, we’ve got a snippet:
2+2
Result of wrapping a snippet using our mechanism looks as follows:
package baz
trait Snippet { self: Bar =>
2+2
}
Besides our main goal, it reduces the boilerplate of a snippet, because you don’t need to import members of the same package and instantiate documented class.
As there might be a situation when your snippet doesn’t work as expected, we’ve exposed an extra flag that prints a wrapped snippet to console in order to debug it.
Hiding code
Sometimes it’s inevitable to import some symbols from different parts of projects or prepare enviroment for a snippets that we want to show to the reader. A big block of imports and initializations of necessary classes can result in loss of reablity. On the other hand, we’ve read a lot of opinions that people would like to be able to see the whole code. For that case we introduced special syntax for snippets that hides certain fragments of code, but you can always expand the snippet in documentation.
Example:
// {{
import scala.collections.immutable.List
// }}
val intList: List[Int] = List(1, 2, 3)
Assert compilation errors
Scala with its rich type system allows us to ensure almost complete correctness of code in compile-time. Creators use this mechanism to detect mistakes at compile-time instead of runtime.
E.g.
```scala sc:compile
val iAmWrong: Int = 2.3
for(i ← 1.to(10))
yield i*2
```
Examples presenting code that fails at compile-time are sometimes very important. For example you can show how the library is secured against incorrect code. The other use case is to present common mistakes and how to solve them. Taking that into account, we decided to provide a functionality in our snippet validation tool to make it ensure that marked code snippets don’t compile.
For snippet that compiles without error:
```scala sc:failing
val iAmGood: Int = 2
for(i ← 1.to(10))
yield i*2
```
For snippet that fails to compile:
```scala sc:failing
val iAmWrong: Int = 2.3
for(i ← 1.to(10))
yield i*2
```
Passes the snippet validation and shows expected compilation errors in documentation.
We also have an idea to expand this feature to not only let the user mark a snippet as failing to compile, but also to let the user point out the place in code and error with which the snippet should fail.
Example:
```scala sc:failing
doesNotExist()
// ^ Not found
```
For this example, the compiler produces an error: Not found: doesNotExist
at line 1 and column 0, which satisfies the provided condition.
How would you name a flag that marks a snippet which should not compile?
Configuration
Snippet compiler affects only scala
snippets (it recognizes them by ```scala part). By default snippet validation is turned off for all snippets. Snippet validation configuration is very straightforward and is similar to configuration of other options in Scaladoc. There are two ways to provide configuration: using CLI arguments or by specifying a flag directly in the snippet.
CLI arguments:
-
-snippet-compiler:arg(,arg)+
arg := (path=)?flag
Where:
path
- optional parameter that defines prefix of paths of source files (relative to sources root) in which snippets should be checked. Argument without path defines default behavior. By setting path
parameter, it’s possible to disable the snippet compiler on a per-directory basis.
flag
- one of available flags:
-
compile
- turns on snippet checking -
nocompile
- turns off snippet checking -
failing
- turns on snippet checking, snippets should not compile
-
snippet-compiler-debug
- Setting this option causes all compiled snippets in documentation to be replaced with their wrapped version (to be able to debug problems)
Directly in the snippet:
```scala sc:flag
//snippet
```
CLI flags are overridden by flags provided directly in the snippet.
And that’s all!
Edit: We edited the section about mdoc and tut, since it was not sounding like we properly recognized these great tools!