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

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}"

For 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 scenario value must exactly match the DocScenario name;
  • 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 carries data-livedocs="snapshot", otherwise a compile check;
  • one compile fact per page compilation unit and per isolated block;
  • one execution fact per run block and per transcript.

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.