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.

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"

(*

## 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>` 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:

* Add a [markdown inline link](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#links) were the link
title is the name of the type you want to link.

      /// this will generate a link to [Foo.Bar] documentation

* Add a [Markdown inline code](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code) (using
back-ticks) where the code is the name of the type you want to link.

      /// This will also generate a link to `Foo.Bar` documentation

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 tripple-slashed line, but it could be either the first or the last:

/// [omit]
/// Some actual comment
module Bar = 
   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=[])
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>
val this : GenericClass2<'T>
val _c : 'T
val _o : '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)" />
[<Struct>]
type Bar = int
 Bar is just an `int` and belongs to module [Foo]
Multiple items
val int : value:'T -> int (requires member op_Explicit)

--------------------
[<Struct>]
type int = int32

--------------------
type int<'Measure> =
  int
type Baz =
  { id: Bar }
 Baz contains a `Foo.Bar` as its `id`
Baz.id: Bar
val f : b:Baz -> Bar
 This function operates on `Baz` types.
val b : Baz
[<Struct>]
type Bar = double
 This is not the same type as `Foo.Bar`
Multiple items
val double : value:'T -> double (requires member op_Explicit)

--------------------
[<Struct>]
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
val a : 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 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 []
  static member GetInvalidPathChars : unit -> char []
  ...
Path.Combine([<System.ParamArray>] paths: string []) : 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
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) * ?strict:bool -> ApiDocModel * ApiDocsSearchIndexEntry []
  static member private GenerateHtmlPhased : 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) * ?strict:bool -> ApiDocModel * Substitutions * ApiDocsSearchIndexEntry [] * (#seq<ParamKey * string> -> unit)
  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) * ?strict:bool -> ApiDocModel * ApiDocsSearchIndexEntry []
  static member private GenerateMarkdownPhased : 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) * ?strict:bool -> ApiDocModel * Substitutions * ApiDocsSearchIndexEntry [] * (#seq<ParamKey * string> -> unit)
  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) * ?strict:bool * ?extension:ApiDocFileExtensions -> ApiDocModel
  static member SearchIndexEntriesForModel : model:ApiDocModel -> ApiDocsSearchIndexEntry []
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) * ?strict:bool -> ApiDocModel * ApiDocsSearchIndexEntry []