Verify F# examples
Choose the least powerful mode that proves your documentation claim. Ordinary examples compile but do not execute.
Compile a page
Use ordinary fsharp fences for progressive examples:
```fsharp
type Order = { Total: decimal }
```
```fsharp
let order = { Total = 120M }
```
FsLiveDocs compiles these blocks in one page unit. It never executes them.
Compile an independent example
Use isolated when the example must stand alone:
```fsharp isolated
let normalize (value: string) = value.Trim()
```
Run an example
Use run only when runtime behavior is part of the contract:
```fsharp run
printfn "order-total=120.0"
```
Operational code can access files, processes, networks, clocks, or hosts. Do not mark ordinary examples run for stronger-looking verification.
Verify an FSI transcript
Use transcript for input and expected output:
```fsharp transcript
> 20 + 22;;
val it: int = 42
```
Add page setup
Use prepare for declarations shared by later blocks:
```fsharp prepare
type Customer = { Name: string }
```
Mark deliberate pseudocode
Use no-check only when the fragment cannot be made complete:
```fsharp no-check reason="The remaining cases are application-specific"
match result with
| Ok value -> publish value
| Error _ -> ...
```
FsLiveDocs requires a nonempty reason. Audit output reports the exclusion.
Verify XML examples
Add an example to XML documentation:
/// <summary>Adds two values.</summary>
/// <example name="add-values" data-livedocs="snapshot">
/// > add 20 22;;
/// val it: int = 42
/// </example>
let add left right = left + right
add: int -> int -> intleft: intright: int(+): ^T1 -> ^T2 -> ^T3Overloaded addition operator The first parameter. The second parameter. The result of the operation. 2 + 2 // Evaluates to 4 "Hello " + "World" // Evaluates to "Hello World"
Use data-livedocs="snapshot" when output is part of the contract. Use data-livedocs="no-check" reason="..." for a deliberate exclusion.
Prepare XML examples with scenarios
Some XML examples need deterministic state before their transcript runs. Install the small annotations package in the library that owns the example:
dotnet add package FsLiveDocs.Annotations
FsLiveDocs.Annotations contains metadata consumed by FsLiveDocs; it does not bring the CLI, compiler service, or renderer into your library. Mark a public, parameterless function with a unique scenario name:
open FsLiveDocs
module CustomerExamples =
let mutable private currentCustomer = "anonymous"
[<DocScenario("preferred-customer")>]
let preparePreferredCustomer () =
currentCustomer <- "Ada"
/// <summary>Greets the current customer.</summary>
/// <example name="preferred-customer-greeting"
/// scenario="preferred-customer"
/// data-livedocs="snapshot">
/// > CustomerExamples.greet();;
/// val it: string = "Hello Ada"
/// </example>
let greet () = $"Hello {currentCustomer}"
FsLiveDocs02-guides_01-verified-examples.md_page.CustomerExamplescurrentCustomer: stringFsLiveDocs.DocScenarioAttributeMarks a parameterless static method as setup for named XML documentation examples.
preparePreferredCustomer: unit -> unitgreet: unit -> stringFor each example that names the scenario, FsLiveDocs starts the example session, loads the documented project, calls preparePreferredCustomer(), and then evaluates the example. Setup output is not part of the expected transcript.
Use scenarios for focused deterministic setup such as fixture data, dependency-injection state, or an in-memory test double. Keep setup fast and local: executable documentation has the same file, process, network, clock, and environment access as the user running FsLiveDocs.
Scenario rules:
- the
scenariovalue must exactly match theDocScenarioname; - scenario names must be unique across the projects in one documentation build;
- the annotated F# function must compile to a callable static, parameterless method; a public function in an F# module is the usual form;
- the example fails when its named scenario cannot be found;
- each example gets a fresh FSI session, so one example must not depend on another example having run first.
Do not add FsLiveDocs itself as a library dependency. It is a .NET tool package. FsLiveDocs.Annotations is the compile-time contract for attributes used by documented projects.
Verify without a test project
dotnet livedocs test
test runs the full verification transiently: it audits every F# block, compiles
each page and isolated unit, then runs every run block and transcript. Nothing
is written. This is all most repositories need in CI.
Generate a committed test project
dotnet livedocs generate-tests
dotnet test tests/FsLiveDocs.SnapshotTests/FsLiveDocs.SnapshotTests.fsproj
Use this when you want documentation verification to appear in your normal test run, IDE test explorer, and coverage reports.
What is generated
generate-tests writes tests/FsLiveDocs.SnapshotTests/ — a .fsproj and a
SnapshotTests.fs containing one xUnit [<Fact>] per discovered case,
enumerated at generation time:
- one per XML
<example>— a Verify snapshot when the example carriesdata-livedocs="snapshot", otherwise a compile check; - one compile fact per page compilation unit and per
isolatedblock; - one execution fact per
runblock and pertranscript.
Discovery is the same pass audit, build, and capture use: top-level fsharp
fences in the Markdown after transclusion and shortcode expansion, plus every
XML <example> on the documented API.
It does not rediscover on its own
The generated file lists cases by name. When it runs, each fact re-runs discovery for its own page and fails if that case has disappeared, so drift is caught — but a new example or fence is only picked up when you regenerate.
Re-run generate-tests whenever you add, remove, or rename an example or an F#
block, then commit the diff. Re-running when nothing changed produces no diff (the
files are rewritten only when their content changes), so it is safe in a pre-commit
hook or a CI freshness check:
dotnet livedocs generate-tests --interactive false --banner false
git diff --exit-code tests/FsLiveDocs.SnapshotTests
FsLiveDocs still owns coverage validation, compile-before-execute ordering,
transcript behavior, and stale-case detection — the generated facts only expose
that work to dotnet test.
See Verify documentation in CI for where these commands fit.
Audit only
dotnet livedocs audit
Audit checks coverage, modes, and compilation for every block, and classifies each
as passed, excluded, or failed. It does not execute run blocks or transcripts —
that is what test adds. A successful release capture requires complete coverage.