Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* Add `{{fsdocs-logo-alt}}` substitution (configurable via `<FsDocsLogoAlt>` MSBuild property, defaults to `Logo`) for accessible alt text on the header logo image. [#626](https://github.com/fsprojects/FSharp.Formatting/issues/626)
* Add `fsdocs init` command to scaffold a minimal `docs/index.md` (and optionally `_template.html`) for new projects. [#872](https://github.com/fsprojects/FSharp.Formatting/issues/872)
* `IFsiEvaluator` now inherits `IDisposable`; `FsiEvaluator` disposes its underlying FSI session when disposed, preventing session leaks in long-running processes. [#341](https://github.com/fsprojects/FSharp.Formatting/issues/341)
* Display of type constraints (e.g. `'T : equality`, `'T : comparison`, `'T :> IComparable`) in generated API documentation. Constraints are shown inline using the F# compiler's compact `(requires ...)` style by default (e.g. `'T (requires equality)`). Controlled by `<FsDocsTypeConstraints>` in the project file with values `None`, `Short` (default, inline compact form), and `Full` (separate "Constraints:" section). [#591](https://github.com/fsprojects/FSharp.Formatting/issues/591)
* Show inherited members from documented base types in a new "Inherited members" section on type pages (MSDN-style). [#590](https://github.com/fsprojects/FSharp.Formatting/issues/590)
* Add `<FsDocsNoInheritedMembers>true</FsDocsNoInheritedMembers>` project file setting to suppress "Inherited from" sections in generated API docs. [#1039](https://github.com/fsprojects/FSharp.Formatting/pull/1039)
* Generate `llms.txt` and `llms-full.txt` for LLM consumption by default (opt out via `<FsDocsGenerateLlmsTxt>false</FsDocsGenerateLlmsTxt>`); when enabled, markdown output is always generated alongside HTML (even without a user-provided `_template.md`) and `llms.txt` links point to the `.md` files. [#951](https://github.com/fsprojects/FSharp.Formatting/issues/951) [#980](https://github.com/fsprojects/FSharp.Formatting/pull/980)
Expand Down
36 changes: 24 additions & 12 deletions docs/commandline.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,30 @@ settings in your .fsproj project files:
| `--sourcerepo` | Source repository for github links (`<FsDocsSourceRepository>`) |
| `--mdcomments` | Assume comments in F# code are markdown (`<UsesMarkdownComments>`) |

## Project file settings

Many fsdocs behaviours can be controlled via MSBuild properties in your `.fsproj` (or `Directory.Build.props`) file.

| Property | Default | Description |
|:---------|:--------|:------------|
| `<GenerateDocumentationFile>true</GenerateDocumentationFile>` | `false` | Required — enables XML doc generation so fsdocs can produce API docs for this project. |
| `<FsDocsAllowExecutableProject>true</FsDocsAllowExecutableProject>` | `false` | Include this project even though its `OutputType` is not `Library`. |
| `<UsesMarkdownComments>true</UsesMarkdownComments>` | `false` | Treat `///` doc comments as Markdown rather than XML doc. Equivalent to `--mdcomments`. |
| `<FsDocsWarnOnMissingDocs>true</FsDocsWarnOnMissingDocs>` | `false` | Emit warnings for public members that have no documentation comments. |
| `<FsDocsSourceFolder>src</FsDocsSourceFolder>` | *(auto)* | Root source folder used when constructing source-link URLs. Equivalent to `--sourcefolder`. |
| `<FsDocsSourceRepository>https://github.com/…/blob/main</FsDocsSourceRepository>` | *(auto from repo)* | Repository URL prefix for source links. Equivalent to `--sourcerepo`. |
| `<FsDocsCollectionNameLink>https://example.com</FsDocsCollectionNameLink>` | *(none)* | URL for the collection-name link in the navigation header. |
| `<FsDocsLogoSource>img/logo.png</FsDocsLogoSource>` | *(none)* | Path to the logo image shown in the header. |
| `<FsDocsLogoAlt>My Project</FsDocsLogoAlt>` | `Logo` | Alt text for the header logo (accessibility). |
| `<FsDocsLogoLink>https://example.com</FsDocsLogoLink>` | *(none)* | URL the logo links to. |
| `<FsDocsFaviconSource>img/favicon.ico</FsDocsFaviconSource>` | *(none)* | Path to the favicon. |
| `<FsDocsTheme>default</FsDocsTheme>` | `default` | Theme to use for generated HTML. |
| `<FsDocsLicenseLink>https://…/LICENSE</FsDocsLicenseLink>` | *(none)* | URL to the project licence, shown in the footer. |
| `<FsDocsReleaseNotesLink>https://…/RELEASE_NOTES.md</FsDocsReleaseNotesLink>` | *(none)* | URL to the release notes, shown in the footer. |
| `<FsDocsNoInheritedMembers>true</FsDocsNoInheritedMembers>` | `false` | Suppress the "Inherited from X" sections on type pages. |
| `<FsDocsTypeConstraints>Short</FsDocsTypeConstraints>` | `Short` | Controls how generic type constraints are displayed in member tooltips. `None` hides constraints entirely; `Short` (default) shows them inline using the compact `(requires ...)` style (e.g. `'T (requires equality)`); `Full` shows them in a separate "Constraints:" section with full `when` syntax. |
| `<FsDocsGenerateLlmsTxt>false</FsDocsGenerateLlmsTxt>` | `true` | Generate `llms.txt` and `llms-full.txt` for LLM consumption alongside the HTML output. |

The command will report on any `.fsproj` files that it finds, telling you if it decides to skip a particular file and why.

For example, a project will be skipped if:
Expand All @@ -67,18 +91,6 @@ For example, a project will be skipped if:
<FsDocsAllowExecutableProject>true</FsDocsAllowExecutableProject>
```

## Controlling inherited-member display

By default, API documentation pages for a type show an **"Inherited from X"** section listing
instance and static members inherited from documented base types within the same docs set.
Members from external types such as `System.Object` are never shown.

To suppress inherited-member sections for a project, add the following property to the `.fsproj` file:

```xml
<FsDocsNoInheritedMembers>true</FsDocsNoInheritedMembers>
```


## The watch command

Expand Down
58 changes: 57 additions & 1 deletion src/FSharp.Formatting.ApiDocs/ApiDocTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@
else if String.IsNullOrEmpty a.Value then None
else Some a.Value

/// Controls how type constraints on generic members are displayed in generated API docs.
[<RequireQualifiedAccess>]
type TypeConstraintDisplayMode =
/// Do not display type constraints.
| None
/// Display constraints inline as a 'when' clause appended to the type parameters (default).
| Short
/// Display constraints in a separate 'Constraints:' section in the member tooltip.
| Full
Comment on lines +113 to +121

Check notice

Code scanning / Ionide.Analyzers.Cli

Short description about StructDiscriminatedUnionAnalyzer Note

Consider adding [] to Discriminated Union

/// Represents some HTML formatted by model generation
type ApiDocHtml(html: string, id: string option) =

Expand Down Expand Up @@ -296,6 +306,8 @@
returnType: (FSharpType * ApiDocHtml) option *
modifiers: string list *
typars: string list *
constraints: string list *
constraintMode: TypeConstraintDisplayMode *
extendedType: (FSharpEntity * ApiDocHtml) option *
location: string option *
compiledName: string option
Expand All @@ -317,7 +329,16 @@
warn
) =

let (ApiDocMemberDetails(usageHtml, paramTypes, returnType, modifiers, typars, extendedType, location, compiledName)) =
let (ApiDocMemberDetails(usageHtml,
paramTypes,
returnType,
modifiers,
typars,
constraints,
constraintMode,
extendedType,
location,
compiledName)) =
details

let m = defaultArg symbol.DeclarationLocation range0
Expand Down Expand Up @@ -413,6 +434,12 @@
/// The member's type arguments
member x.TypeArguments: string list = typars

/// The member's type constraints
member x.Constraints: string list = constraints

/// How type constraints are displayed for this member
member x.TypeConstraintDisplayMode: TypeConstraintDisplayMode = constraintMode

/// The usage section in a typical tooltip
member x.UsageHtml: ApiDocHtml = usageHtml

Expand Down Expand Up @@ -451,6 +478,35 @@
else
Some res

/// Formats type constraints as a 'when' clause (full form, e.g. "'T : equality")
member x.FormatTypeConstraints =
if x.Constraints.IsEmpty then
None
else
Some(String.concat " and " x.Constraints)

/// Formats type constraints in the short 'requires' form used by the F# compiler's shortConstraints mode.
/// Strips the type-parameter prefix from each constraint (e.g. "'T : equality" → "equality",
/// "'T :> IComparable" → ":> IComparable") and joins them with " and ".
/// Returns None when there are no constraints.
member x.FormatShortTypeConstraints =
if x.Constraints.IsEmpty then
None
else
let toShort (full: string) =
// Coercion: "'T :> Type" → ":> Type"
let coerceIdx = full.IndexOf(" :> ", StringComparison.Ordinal)

if coerceIdx >= 0 then
full[coerceIdx + 1 ..]
else
// Regular constraint: "'T : equality" → "equality"
let colonIdx = full.IndexOf(" : ", StringComparison.Ordinal)
if colonIdx >= 0 then full[colonIdx + 3 ..] else full

let shortForms = x.Constraints |> List.map toShort
Some(String.concat " and " shortForms)

/// Formats modifiers
member x.FormatModifiers = String.concat " " x.Modifiers

Expand Down
23 changes: 19 additions & 4 deletions src/FSharp.Formatting.ApiDocs/GenerateHtml.fs
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,27 @@ type HtmlRender(model: ApiDocModel, ?menuTemplateFolder: string) =
encode (m.FormatModifiers)
br []

// We suppress the display of ill-formatted type parameters for places
// where these have not been explicitly declared
match m.FormatTypeArguments with
// We suppress the display of ill-formatted type parameters for places
// where these have not been explicitly declared
match m.FormatTypeArguments with
| None -> ()
| Some v ->
!!"Type parameters: "

if m.TypeConstraintDisplayMode = TypeConstraintDisplayMode.Short then
match m.FormatShortTypeConstraints with
| None -> encode v
| Some c -> encode ($"%s{v} (requires %s{c})")
else
encode v

br []

if m.TypeConstraintDisplayMode = TypeConstraintDisplayMode.Full then
match m.FormatTypeConstraints with
| None -> ()
| Some v ->
!!"Type parameters: "
!!"Constraints: "
encode (v)
]
]
Expand Down
15 changes: 15 additions & 0 deletions src/FSharp.Formatting.ApiDocs/GenerateMarkdown.fs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ type MarkdownRender(model: ApiDocModel, ?menuTemplateFolder: string) =
| None -> ()
| Some r -> p [ embed r ]

match m.FormatTypeArguments with
| None -> ()
| Some v ->
if m.TypeConstraintDisplayMode = TypeConstraintDisplayMode.Short then
match m.FormatShortTypeConstraints with
| None -> p [ !!("Type parameters: " + v) ]
| Some c -> p [ !!(sprintf "Type parameters: %s (requires %s)" v c) ]
else
p [ !!("Type parameters: " + v) ]

if m.TypeConstraintDisplayMode = TypeConstraintDisplayMode.Full then
match m.FormatTypeConstraints with
| None -> ()
| Some c -> p [ !!"Constraints: "; !!c ]

if not m.Comment.Exceptions.IsEmpty then
for (nm, url, html) in m.Comment.Exceptions do
p
Expand Down
13 changes: 10 additions & 3 deletions src/FSharp.Formatting.ApiDocs/GenerateModel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type ApiDocInput =
/// Whether to show members inherited from documented base types.
/// Defaults to true. Set to false to suppress "Inherited from X" sections.
ShowInheritedMembers: bool

/// Controls how type constraints on generic members are displayed.
/// Defaults to Short, which appends constraints inline as a 'when' clause.
TypeConstraintDisplayMode: TypeConstraintDisplayMode
}

static member FromFile
Expand All @@ -66,7 +70,8 @@ type ApiDocInput =
?sourceFolder,
?publicOnly,
?warn,
?showInheritedMembers
?showInheritedMembers,
?typeConstraintDisplayMode
) =
{ Path = assemblyPath
XmlFile = None
Expand All @@ -76,7 +81,8 @@ type ApiDocInput =
Substitutions = substitutions
PublicOnly = defaultArg publicOnly true
MarkdownComments = defaultArg mdcomments false
ShowInheritedMembers = defaultArg showInheritedMembers true }
ShowInheritedMembers = defaultArg showInheritedMembers true
TypeConstraintDisplayMode = defaultArg typeConstraintDisplayMode TypeConstraintDisplayMode.Short }



Expand Down Expand Up @@ -225,7 +231,8 @@ type ApiDocModel internal (substitutions, collection, entityInfos, root, qualify
urlMap,
codeFormatCompilerArgs,
project.Warn,
project.ShowInheritedMembers
project.ShowInheritedMembers,
project.TypeConstraintDisplayMode
)
|> Some)

Expand Down
30 changes: 24 additions & 6 deletions src/FSharp.Formatting.ApiDocs/SymbolReader.fs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ module internal SymbolReader =
AssemblyPath: string
CompilerOptions: string
Substitutions: Substitutions
ShowInheritedMembers: bool }
ShowInheritedMembers: bool
TypeConstraintDisplayMode: TypeConstraintDisplayMode }

member x.XmlMemberLookup(key) =
match x.XmlMemberMap.TryGetValue(key) with
Expand All @@ -57,7 +58,8 @@ module internal SymbolReader =
fscOptions,
substitutions,
warn,
showInheritedMembers
showInheritedMembers,
typeConstraintDisplayMode
) =

{ PublicOnly = publicOnly
Expand All @@ -71,7 +73,8 @@ module internal SymbolReader =
AssemblyPath = assemblyPath
CompilerOptions = fscOptions
Substitutions = substitutions
ShowInheritedMembers = showInheritedMembers }
ShowInheritedMembers = showInheritedMembers
TypeConstraintDisplayMode = typeConstraintDisplayMode }

let inline private getCompiledName (s: ^a :> FSharpSymbol) =
let compiledName = (^a: (member CompiledName: string) (s))
Expand Down Expand Up @@ -285,7 +288,12 @@ module internal SymbolReader =

let typars = formatTypeArgumentsAsText tps

//let cxs = indexedConstraints v.GenericParameters
let constraints =
match ctx.TypeConstraintDisplayMode with
| TypeConstraintDisplayMode.None -> []
| TypeConstraintDisplayMode.Short
| TypeConstraintDisplayMode.Full -> formatConstraintsAsText tps

let retTypeHtml = retType |> Option.map (formatTypeAsHtml ctx.UrlMap >> codeHtml)

let returnType =
Expand Down Expand Up @@ -328,6 +336,8 @@ module internal SymbolReader =
returnType,
modifiers,
typars,
constraints,
ctx.TypeConstraintDisplayMode,
extendedType,
location,
getCompiledName v
Expand Down Expand Up @@ -392,6 +402,8 @@ module internal SymbolReader =
returnType,
modifiers,
typeParams,
List.empty,
TypeConstraintDisplayMode.None,
None,
location,
getCompiledName case
Expand Down Expand Up @@ -430,6 +442,8 @@ module internal SymbolReader =
returnType,
modifiers,
typeParams,
List.empty,
TypeConstraintDisplayMode.None,
None,
location,
if field.Name <> field.DisplayName then
Expand Down Expand Up @@ -475,6 +489,8 @@ module internal SymbolReader =
returnType,
modifiers,
typeParams,
List.empty,
TypeConstraintDisplayMode.None,
None,
location,
if staticParam.Name <> staticParam.DisplayName then
Expand Down Expand Up @@ -1626,7 +1642,8 @@ module internal SymbolReader =
urlMap,
codeFormatCompilerArgs,
warn,
showInheritedMembers
showInheritedMembers,
typeConstraintDisplayMode
) =
let assemblyName = AssemblyName(assembly.QualifiedName)

Expand Down Expand Up @@ -1670,7 +1687,8 @@ module internal SymbolReader =
codeFormatCompilerArgs,
substitutions,
warn,
showInheritedMembers
showInheritedMembers,
typeConstraintDisplayMode
)

//
Expand Down
Loading