Header menu logo FSharp.Formatting

BinderScriptNotebook

API Documentation Generation

The command-line tool fsdocs can be used to generate documentation for F# libraries with XML comments. The documentation is normally built using fsdocs build and developed using fsdocs watch. For the former the output will be placed in output\reference by default.

Selected projects

fsdocs automatically selects the projects and "cracks" the project files for information

    <PropertyGroup>
      <GenerateDocumentationFile>true</GenerateDocumentationFile>
    </PropertyGroup>

Templates

The HTML is built by instantiating a template. The template used is the first of:

Usually the same template can be used as for other content.

Classic XML Doc Comments

XML Doc Comments may use the normal F# and C# XML doc standards.

The tags that form the core of the XML doc specification are:

<c>	<para>	<see>*	<value>
<code>	<param>*	<seealso>*
<example>	<paramref>	<summary>
<exception>*	<permission>*	<typeparam>*
<include>*	<remarks>	<typeparamref>
<list>	<inheritdoc>	<returns>

In addition, you may also use the Recommended XML doc extensions for F# documentation tooling.

An example of an XML documentation comment, assuming the code is in namespace TheNamespace:

/// <summary>
///   A module
/// </summary>
///
/// <namespacedoc>
///   <summary>A namespace to remember</summary>
///
///   <remarks>More on that</remarks>
/// </namespacedoc>
///
module SomeModule =
    /// <summary>
    ///   Some actual comment
    ///   <para>Another paragraph, see  <see cref="T:TheNamespace.SomeType"/>. </para>
    /// </summary>
    ///
    /// <param name="x">The input</param>
    ///
    /// <returns>The output</returns>
    ///
    /// <example>
    ///   Try using
    ///   <code>
    ///      open TheNamespace
    ///      SomeModule.a
    ///   </code>
    /// </example>
    ///
    /// <category>Foo</category>
    let someFunction x = 42 + x

/// <summary>
///   A type, see  <see cref="T:TheNamespace.SomeModule"/> and
///  <see cref="M:TheNamespace.SomeModule.someFunction"/>.
/// </summary>
///
type SomeType() =
    member x.P = 1

Like types, members are referred to by xml doc sig. These must currently be precise as the F# compiler doesn't elaborate these references from simpler names:

type Class2() =
    member this.Property = "more"
    member this.Method0() = "more"
    member this.Method1(c: string) = "more"
    member this.Method2(c: string, o: obj) = "more"

/// <see cref="P:TheNamespace.Class2.Property" />
/// and <see cref="M:TheNamespace.Class2.OtherMethod0" />
/// and <see cref="M:TheNamespace.Class2.Method1(System.String)" />
/// and <see cref="M:TheNamespace.Class2.Method2(System.String,System.Object)" />
let referringFunction1 () = "result"

Generic types are referred to by .NET compiled name, e.g.

type GenericClass2<'T>() =
    member this.Property = "more"

    member this.NonGenericMethod(_c: 'T) = "more"

    member this.GenericMethod(_c: 'T, _o: 'U) = "more"

/// See <see cref="T:TheNamespace.GenericClass2`1" />
/// and <see cref="P:TheNamespace.GenericClass2`1.Property" />
/// and <see cref="M:TheNamespace.GenericClass2`1.NonGenericMethod(`0)" />
/// and <see cref="M:TheNamespace.GenericClass2`1.GenericMethod``1(`0,``0)" />
let referringFunction2 () = "result"

Cross-referencing with <seealso>

Use <seealso cref="..."/> within <summary> to create cross-references.

For example:

module Forest =

    /// <summary>
    /// Find at most <c>limit</c> foxes in current forest
    ///
    /// See also: <seealso cref="M:App.Forest.findSquirrels(System.Int32)"/>
    /// </summary>
    let findFoxes (limit : int) = []

    /// <summary>
    /// Find at most <c>limit</c> squirrels in current forest
    ///
    /// See also: <seealso cref="M:App.Forest.findFoxes(System.Int32)"/>
    /// </summary>
    let findSquirrels (limit : int) = []

You can find the correct value for cref in the generated .xml documentation file (this will be generated alongside the assembly's .dll`).

You can also omit the cref's arguments, and fsdocs will make an attempt to find the first member that matches.

For example:

    /// See also: <seealso cref="M:App.Forest.findSquirrels"/>

If the member cannot be found, a link to the containing module/type will be used instead.

Classic XMl Doc Comments: Excluding APIs from the docs

If you want to exclude modules or functions from the API docs you can use the <exclude/> tag. It needs to be set on a separate triple-slashed line, and can either appear on its own or as part of an existing <summary> (for example, you may wish to hide existing documentation while it's in progress). The <exclude/> tag can be the first or last line in these cases.

Some examples:

/// <exclude/>
module BottleKids1 =
    let a = 42

// Ordinary comment
/// <exclude/>
module BottleKids2 =
    let a = 43

/// <exclude/>
/// BottleKids3 provides improvements over BottleKids2
module BottleKids3 =
    let a = 44

/// BottleKids4 implements several new features over BottleKids3
/// <exclude/>
module BottleKids4 =
    let a = 45

/// <exclude/>
/// <summary>
/// BottleKids5 is all you'll ever need in terms of bottles or kids.
/// </summary>
module BottleKids5 =
    let a = 46

Note that the comments for BottleKids3 (and BottleKids4) will generate a warning. This is because the <exclude/> tag will be parsed as part of the summary text, and so the documentation generator can't be completely sure you meant to exclude the item, or whether it was a valid part of the documentation. It will assume the exclusion was intended, but you may want to use explicit <summary> tags to remove the warning.

The warning will be of the following format:

Warning: detected "<exclude/>" in text of "<summary>" for "M:YourLib.BottleKids4". Please see https://fsprojects.github.io/FSharp.Formatting/apidocs.html#Classic-XML-Doc-Comments

You will find that [omit] also works, but is considered part of the Markdown syntax and is deprecated for XML Doc comments. This will also produce a warning, such as this:

The use of `[omit]` and other commands in XML comments is deprecated, please use XML extensions, see https://github.com/fsharp/fslang-design/blob/master/tooling/FST-1031-xmldoc-extensions.md

Go to Source links

'fsdocs' normally automatically adds GitHub links to each functions, values and class members for further reference.

This is normally done automatically based on the following settings:

    <RepositoryUrl>https://github.com/...</RepositoryUrl>
    <RepositoryBranch>...</RepositoryBranch>
    <RepositoryType>git</RepositoryType>

If your source is not built from the same project where you are building documentation then you may need these settings:

    <FsDocsSourceRepository>...</FsDocsSourceRepository> -- the URL for the root of the source
    <FsDocsSourceFolder>...</FsDocsSourceFolder>         -- the root soure folder at time of build

It is assumed that sourceRepo and sourceFolder have synchronized contents.

Markdown Comments

You can use Markdown instead of XML in /// comments. If you do, you should set <UsesMarkdownComments>true</UsesMarkdownComments> in your F# project file.

Note: Markdown Comments are not supported in all F# IDE tooling.

Adding cross-type links to modules and types in the same assembly

You can automatically add cross-type links to the documentation pages of other modules and types in the same assembly. You can do this in two different ways:

You can use either the full name (including namespace and module) or the simple name of a type. If more than one type is found with the same name the link will not be generated. If a type with the given name is not found in the same assembly the link will not be generated.

/// Contains two types [Bar] and [Foo.Baz]
module Foo =

    /// Bar is just an `int` and belongs to module [Foo]
    type Bar = int

    /// Baz contains a `Foo.Bar` as its `id`
    type Baz = { id: Bar }

    /// This function operates on `Baz` types.
    let f (b: Baz) = b.id * 42

/// Referencing [Foo3] will not generate a link as there is no type with the name `Foo3`
module Foo3 =

    /// This is not the same type as `Foo.Bar`
    type Bar = double

    /// Using the simple name for [Bar] will fail to create a link because the name is duplicated in
    /// [Foo.Bar] and in [Foo3.Bar]. In this case, using the full name works.
    let f2 b = b * 50

Markdown Comments: Excluding APIs from the docs

If you want to exclude modules or functions from the API docs you can use the [omit] tag. It needs to be set on a separate triple-slashed line, but it could be either the first or the last:

Example as last line:

/// Some actual comment
/// [omit]
module Bar =
    let a = 42

Example as first line:

/// [omit]
/// Some actual comment
module Bar2 =
    let a = 42

Building library documentation programmatically

You can build library documentation programatically using the functionality in the ApiDocs type. To do this, load the assembly and open necessary namespaces:

#r "FSharp.Formatting.ApiDocs.dll"

open FSharp.Formatting.ApiDocs
open System.IO

For example the ApiDocs.GenerateHtml method:

let file = Path.Combine(root, "bin/YourLibrary.dll")

let input = ApiDocInput.FromFile(file)

ApiDocs.GenerateHtml(
    [ input ],
    output = Path.Combine(root, "output"),
    collectionName = "YourLibrary",
    template = Path.Combine(root, "templates", "template.html"),
    substitutions = []
)

Adding extra dependencies

When building a library programmatically, you might require a reference to an additional assembly. You can pass this using the otherFlags argument.

let projectAssembly = Path.Combine(root, "bin/X.dll")

let projectInput = ApiDocInput.FromFile(projectAssembly)

ApiDocs.GenerateHtml(
    [ projectInput ],
    output = Path.Combine(root, "output"),
    collectionName = "Project X",
    template = Path.Combine(root, "templates", "template.html"),
    substitutions = [],
    otherFlags = [ "-r:/root/ProjectY/bin/Debug/net6.0/Y.dll" ]
)

or use libDirs to include all assemblies from an entire folder. Tip: A combination of <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> in the fsproj file and setting libDirs to the compilation output path leads to only one folder with all dependencies referenced. This might be easier especially for large projects with many dependencies.

ApiDocs.GenerateHtml(
    [ projectInput ],
    output = Path.Combine(root, "output"),
    collectionName = "Project X",
    template = Path.Combine(root, "templates", "template.html"),
    substitutions = [],
    libDirs = [ "ProjectX/bin/Debug/netstandard2.0" ]
)

Rebasing Links

The root parameter is used for the base of page and image links in the generated documentation. By default it is derived from the project's <PackageProjectUrl> property.

In some instances, you may wish to override the value for root (perhaps for local testing). To do this, you can use the command-line argument --parameters root <base>.

For example:

dotnet fsdocs build --output public/docs --parameters root ../
val root: string
type 'T list = List<'T>
val someFunction: x: int -> int
 <summary>
   Some actual comment
   <para>Another paragraph, see <see cref="T:TheNamespace.SomeType"/>. </para>
 </summary>

 <param name="x">The input</param>

 <returns>The output</returns>

 <example>
   Try using
   <code>
      open TheNamespace
      SomeModule.a
   </code>
 </example>

 <category>Foo</category>
val x: int
Multiple items
type SomeType = new: unit -> SomeType member P: int
 <summary>
   A type, see <see cref="T:TheNamespace.SomeModule"/> and
  <see cref="M:TheNamespace.SomeModule.someFunction"/>.
 </summary>


--------------------
new: unit -> SomeType
val x: SomeType
member SomeType.P: int
Multiple items
type Class2 = new: unit -> Class2 member Method0: unit -> string member Method1: c: string -> string member Method2: c: string * o: obj -> string member Property: string

--------------------
new: unit -> Class2
val this: Class2
val c: string
Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
val o: obj
type obj = System.Object
val referringFunction1: unit -> string
 <see cref="P:TheNamespace.Class2.Property" />
 and <see cref="M:TheNamespace.Class2.OtherMethod0" />
 and <see cref="M:TheNamespace.Class2.Method1(System.String)" />
 and <see cref="M:TheNamespace.Class2.Method2(System.String,System.Object)" />
Multiple items
type GenericClass2<'T> = new: unit -> GenericClass2<'T> member GenericMethod: _c: 'T * _o: 'U -> string member NonGenericMethod: _c: 'T -> string member Property: string

--------------------
new: unit -> GenericClass2<'T>
'T
val this: GenericClass2<'T>
val _c: 'T
val _o: 'U
'U
val referringFunction2: unit -> string
 See <see cref="T:TheNamespace.GenericClass2`1" />
 and <see cref="P:TheNamespace.GenericClass2`1.Property" />
 and <see cref="M:TheNamespace.GenericClass2`1.NonGenericMethod(`0)" />
 and <see cref="M:TheNamespace.GenericClass2`1.GenericMethod``1(`0,``0)" />
val findFoxes: limit: int -> 'a list
 <summary>
 Find at most <c>limit</c> foxes in current forest

 See also: <seealso cref="M:App.Forest.findSquirrels(System.Int32)"/>
 </summary>
val limit: int
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
val findSquirrels: limit: int -> 'a list
 <summary>
 Find at most <c>limit</c> squirrels in current forest

 See also: <seealso cref="M:App.Forest.findFoxes(System.Int32)"/>
 </summary>
val a: int
type Bar = int
 Bar is just an `int` and belongs to module [Foo]
type Baz = { id: Bar }
 Baz contains a `Foo.Bar` as its `id`
val id: x: 'T -> 'T
val f: b: Baz -> Bar
 This function operates on `Baz` types.
val b: Baz
Baz.id: Bar
type Bar = double
 This is not the same type as `Foo.Bar`
Multiple items
val double: value: 'T -> double (requires member op_Explicit)

--------------------
type double = System.Double

--------------------
type double<'Measure> = float<'Measure>
val f2: b: int -> int
 Using the simple name for [Bar] will fail to create a link because the name is duplicated in
 [Foo.Bar] and in [Foo3.Bar]. In this case, using the full name works.
val b: int
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace FSharp.Formatting
namespace FSharp.Formatting.ApiDocs
namespace System
namespace System.IO
val file: string
type Path = static member ChangeExtension: path: string * extension: string -> string static member Combine: path1: string * path2: string -> string + 3 overloads static member EndsInDirectorySeparator: path: ReadOnlySpan<char> -> bool + 1 overload static member Exists: path: string -> bool static member GetDirectoryName: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload static member GetExtension: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload static member GetFileName: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload static member GetFileNameWithoutExtension: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload static member GetFullPath: path: string -> string + 1 overload static member GetInvalidFileNameChars: unit -> char array ...
<summary>Performs operations on <see cref="T:System.String" /> instances that contain file or directory path information. These operations are performed in a cross-platform manner.</summary>
Path.Combine([<System.ParamArray>] paths: string array) : string
Path.Combine(path1: string, path2: string) : string
Path.Combine(path1: string, path2: string, path3: string) : string
Path.Combine(path1: string, path2: string, path3: string, path4: string) : string
val input: ApiDocInput
type ApiDocInput = { Path: string XmlFile: string option SourceFolder: string option SourceRepo: string option Substitutions: Substitutions option MarkdownComments: bool Warn: bool PublicOnly: bool } static member FromFile: assemblyPath: string * ?mdcomments: bool * ?substitutions: Substitutions * ?sourceRepo: string * ?sourceFolder: string * ?publicOnly: bool * ?warn: bool -> ApiDocInput
<summary> Represents an input assembly for API doc generation </summary>
static member ApiDocInput.FromFile: assemblyPath: string * ?mdcomments: bool * ?substitutions: FSharp.Formatting.Templating.Substitutions * ?sourceRepo: string * ?sourceFolder: string * ?publicOnly: bool * ?warn: bool -> ApiDocInput
type ApiDocs = static member GenerateHtml: inputs: ApiDocInput list * output: string * collectionName: string * substitutions: Substitutions * ?template: string * ?root: string * ?qualify: bool * ?libDirs: string list * ?otherFlags: string list * ?urlRangeHighlight: (Uri -> int -> int -> string) * ?onError: (string -> unit) -> ApiDocModel * ApiDocsSearchIndexEntry array static member GenerateMarkdown: inputs: ApiDocInput list * output: string * collectionName: string * substitutions: Substitutions * ?template: string * ?root: string * ?qualify: bool * ?libDirs: string list * ?otherFlags: string list * ?urlRangeHighlight: (Uri -> int -> int -> string) * ?onError: (string -> unit) -> ApiDocModel * ApiDocsSearchIndexEntry array static member GenerateModel: inputs: ApiDocInput list * collectionName: string * substitutions: Substitutions * ?qualify: bool * ?libDirs: string list * ?otherFlags: string list * ?root: string * ?urlRangeHighlight: (Uri -> int -> int -> string) * ?onError: (string -> unit) * ?extension: ApiDocFileExtensions -> ApiDocModel static member SearchIndexEntriesForModel: model: ApiDocModel -> ApiDocsSearchIndexEntry array
<summary> This type exposes the functionality for producing documentation model from `dll` files with associated `xml` files generated by the F# or C# compiler. To generate documentation model, use one of the overloades of the `Generate` method. </summary>
<namespacedoc><summary>Functionality relating to generating API documentation</summary></namespacedoc>
static member ApiDocs.GenerateHtml: inputs: ApiDocInput list * output: string * collectionName: string * substitutions: FSharp.Formatting.Templating.Substitutions * ?template: string * ?root: string * ?qualify: bool * ?libDirs: string list * ?otherFlags: string list * ?urlRangeHighlight: (System.Uri -> int -> int -> string) * ?onError: (string -> unit) -> ApiDocModel * ApiDocsSearchIndexEntry array
val projectAssembly: string
val projectInput: ApiDocInput

Type something to start searching.