Skip to content
Open
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
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [Unreleased]

### Added
* Support top-level `<seealso cref="..."/>` XML doc tags in API documentation generation. Per the [xmldoc recommended tags](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/recommended-tags#seealso), `<seealso>` is a top-level section (unlike the inline `<see>` tag) and is now rendered as a "See also" list in both HTML and Markdown API doc output, for both members and entities. [#1256](https://github.com/fsprojects/FSharp.Formatting/issues/1256)

### Removed
* Remove `docs/Dockerfile` (used for mybinder.org Binder integration) and mybinder badge links from documentation pages. The Binder integration relied on a deprecated .NET 7 SDK image and a deprecated `Microsoft.dotnet-interactive` version; mybinder.org support is discontinued.
* Remove `.ipynb` (Jupyter Notebook) "run in notebook" badge links from all documentation pages and delete `docs/img/badge-notebook.svg`. The links linked to `.ipynb` outputs generated by fsdocs, but .NET Interactive (which powered those notebooks) is deprecated and has no current replacement. The `.ipynb` output format itself is unchanged.
Expand Down
7 changes: 5 additions & 2 deletions src/FSharp.Formatting.ApiDocs/ApiDocTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ type ApiDocHtml(html: string, id: string option) =
member _.Id = id

/// Represents a documentation comment attached to source code
type ApiDocComment(xmldoc, summary, remarks, parameters, returns, examples, notes, exceptions, rawData) =
type ApiDocComment(xmldoc, summary, remarks, parameters, returns, examples, notes, exceptions, seeAlso, rawData) =

/// The XElement for the XML doc if available
member _.Xml: XElement option = xmldoc
Expand All @@ -155,10 +155,13 @@ type ApiDocComment(xmldoc, summary, remarks, parameters, returns, examples, note
/// The notes sections of the comment
member _.Exceptions: (string * string option * ApiDocHtml) list = exceptions

/// The top-level "seealso" sections of the comment, rendered as a "See also:" list
member _.SeeAlso: (string * string option * ApiDocHtml) list = seeAlso

/// The raw data of the comment
member _.RawData: KeyValuePair<string, string> list = rawData

static member internal Empty = ApiDocComment(None, ApiDocHtml("", None), None, [], None, [], [], [], [])
static member internal Empty = ApiDocComment(None, ApiDocHtml("", None), None, [], None, [], [], [], [], [])

/// Represents a custom attribute attached to source code
type ApiDocAttribute(name, fullName, constructorArguments, namedConstructorArguments) =
Expand Down
24 changes: 24 additions & 0 deletions src/FSharp.Formatting.ApiDocs/GenerateHtml.fs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,18 @@ type HtmlRender(model: ApiDocModel, ?menuTemplateFolder: string) =

p [ Class "fsdocs-note" ] [ embed e ]

if not m.Comment.SeeAlso.IsEmpty then
h5 [ Class "fsdocs-seealso-header" ] [ !!"See also" ]

ul [ Class "fsdocs-seealso-list" ] [
for (nm, link, html) in m.Comment.SeeAlso do
li [] [
match link with
| Some href -> a [ Href href ] [ !!nm ]
| None -> embed html
]
]

for e in m.Comment.Examples do
h5 [ Class "fsdocs-example-header" ] [ !!"Example" ]

Expand Down Expand Up @@ -467,6 +479,18 @@ type HtmlRender(model: ApiDocModel, ?menuTemplateFolder: string) =

p [ Class "fsdocs-note" ] [ embed note ]

if not entity.Comment.SeeAlso.IsEmpty then
h5 [ Class "fsdocs-seealso-header" ] [ !!"See also" ]

ul [ Class "fsdocs-seealso-list" ] [
for (nm, link, html) in entity.Comment.SeeAlso do
li [] [
match link with
| Some href -> a [ Href href ] [ !!nm ]
| None -> embed html
]
]

for example in entity.Comment.Examples do
h5 [ Class "fsdocs-example-header" ] [ !!"Example" ]

Expand Down
18 changes: 18 additions & 0 deletions src/FSharp.Formatting.ApiDocs/GenerateMarkdown.fs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,15 @@ type MarkdownRender(model: ApiDocModel, ?menuTemplateFolder: string) =
``#####`` [ !!"Note" ]
p [ embed e ]

if not m.Comment.SeeAlso.IsEmpty then
``#####`` [ !!"See also" ]

ul
[ for (nm, url, html) in m.Comment.SeeAlso do
[ match url with
| Some href -> p [ link [ !!nm ] href ]
| None -> p [ embed html ] ] ]

for e in m.Comment.Examples do
``#####`` [ !!"Example" ]
p [ embed e ]
Expand Down Expand Up @@ -251,6 +260,15 @@ type MarkdownRender(model: ApiDocModel, ?menuTemplateFolder: string) =
``#####`` [ !!"Note" ]
p [ embed note ]

if not entity.Comment.SeeAlso.IsEmpty then
``#####`` [ !!"See also" ]

ul
[ for (nm, url, html) in entity.Comment.SeeAlso do
[ match url with
| Some href -> p [ link [ !!nm ] href ]
| None -> p [ embed html ] ] ]

for example in entity.Comment.Examples do
``#####`` [ !!"Example" ]
p [ embed example ]
Expand Down
41 changes: 40 additions & 1 deletion src/FSharp.Formatting.ApiDocs/XmlDocReader.fs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ module internal XmlDocReader =
examples = examples,
notes = notes,
exceptions = [],
seeAlso = [],
rawData = raw
)

Expand Down Expand Up @@ -366,6 +367,41 @@ module internal XmlDocReader =
readXmlElementAsHtml true urlMap cmds html e
cname, None, ApiDocHtml(html.ToString(), None) ]

let seeAlso =
// Only top-level <seealso> elements (direct children of the doc comment), per the
// xmldoc recommended-tags convention: <seealso> is a section, <see> is an inline link.
let seeAlsoNodes = doc.Elements(XName.Get "seealso") |> Seq.toList

[ for e in seeAlsoNodes do
let cref = e.Attribute(XName.Get "cref")

if not (isNull cref) then
if String.IsNullOrEmpty(cref.Value) || cref.Value.Length < 3 then
printfn "Warning: Invalid cref specified in: %A" doc
else
// Older FSharp.Core cref listings don't start with "T:", see https://github.com/dotnet/fsharp/issues/9805
let cname = cref.Value
let cname = if cname.Contains(":") then cname else "T:" + cname

match urlMap.ResolveCref cname with
| Some reference ->
let html = new StringBuilder()
readXmlElementAsHtml true urlMap cmds html e
reference.NiceName, Some reference.ReferenceLink, ApiDocHtml(html.ToString(), None)
| _ ->
let html = new StringBuilder()
readXmlElementAsHtml true urlMap cmds html e
cname, None, ApiDocHtml(html.ToString(), None)
else
// no cref: render the element's own content (e.g. <seealso href="...">text</seealso>)
let html = new StringBuilder()
readXmlElementAsHtml true urlMap cmds html e
let href = e.Attribute(XName.Get "href")

let link = if isNull href then None else Some href.Value

(if isNull href then "" else href.Value), link, ApiDocHtml(html.ToString(), None) ]

let examples =
let exampleNodes = doc.Elements(XName.Get "example") |> Seq.toList

Expand Down Expand Up @@ -404,7 +440,8 @@ module internal XmlDocReader =
&& ln <> "example"
&& ln <> "note"
&& ln <> "returns"
&& ln <> "remarks")
&& ln <> "remarks"
&& ln <> "seealso")
|> Seq.groupBy (fun n -> n.Name.LocalName)
|> Seq.iter (fun (n, lst) ->
let lst = Seq.toList lst
Expand All @@ -425,6 +462,7 @@ module internal XmlDocReader =
examples = examples,
notes = notes,
exceptions = exceptions,
seeAlso = seeAlso,
rawData = rawData
)

Expand Down Expand Up @@ -459,6 +497,7 @@ module internal XmlDocReader =
returns = combineHtmlOptions c1.Returns c2.Returns,
notes = c1.Notes @ c2.Notes,
exceptions = c1.Exceptions @ c2.Exceptions,
seeAlso = c1.SeeAlso @ c2.SeeAlso,
rawData = c1.RawData @ c2.RawData
)

Expand Down
23 changes: 23 additions & 0 deletions tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1636,6 +1636,29 @@ let ``ApiDocs Markdown uses section-based member layout instead of tables`` () =
// No markdown table header for members (the old layout had this)
nestedContent |> shouldNotContainText "Function or value | Description | Source"

[<Test>]
let ``ApiDocs Markdown generates See also section for top-level seealso tags`` () =
let library = testBin </> "FsLib2.dll" |> fullpath

let files = generateApiDocs [ library ] OutputFormat.Markdown false "FsLib2_markdown_seealso"

let seeAlsoContent = files.["fslib-seealsoexamples.md"]

seeAlsoContent |> shouldContainText "##### See also"
seeAlsoContent |> shouldContainText "dothing"
seeAlsoContent |> shouldContainText "dothing2"

[<Test>]
let ``ApiDocs HTML generates See also section for top-level seealso tags`` () =
let library = testBin </> "FsLib2.dll" |> fullpath

let files = generateApiDocs [ library ] OutputFormat.Html false "FsLib2_html_seealso"

let seeAlsoContent = files.["fslib-seealsoexamples.html"]

seeAlsoContent |> shouldContainText "See also"
seeAlsoContent |> shouldContainText "fsdocs-seealso-list"

[<Test>]
let ``ApiDocs Markdown generates Example and Note section headings`` () =
let library = testBin </> "FsLib2.dll" |> fullpath
Expand Down
6 changes: 6 additions & 0 deletions tests/FSharp.ApiDocs.Tests/files/FsLib2/Library2.fs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ module CommentExamples =
///
let attribInCodeExample () = ()

module SeeAlsoExamples =
/// <summary>this does the thing</summary>
/// <seealso cref="M:FsLib.CommentExamples.dothing"/>
/// <seealso cref="M:FsLib.CommentExamples.dothing2"/>
let dothingwithseealso () = ()

/// Base class for testing inherited members (issue #590)
type BaseClassForInheritance() =
/// A documented instance method on the base class
Expand Down
Loading