Skip to content

Commit 8b7b708

Browse files
github-actions[bot]Copilotdsyme
authored
[Repo Assist] Add /// doc comments to XmlDocReader.fs and GenerateMarkdown.fs (issue #1035) (#1079)
* Add `///` doc comments to XmlDocReader.fs and GenerateMarkdown.fs (issue #1035) Add XML documentation comments to all public-facing and module-level functions in XmlDocReader.fs and GenerateMarkdown.fs, which had very sparse coverage (2 and 9 triple-slash comments respectively). XmlDocReader.fs: removeSpaces, readMarkdownCommentAsHtml, findCommand, readXmlElementAsHtml, SummaryWithoutChildren, readXmlCommentAsHtmlAux, combineHtml, combineHtmlOptions, combineComments, combineNamespaceDocs, readXmlCommentAsHtml, readNamespaceDocs. GenerateMarkdown.fs: htmlStringSafe, embed, embedSafe, br, sourceLink, renderMembers, renderEntities, entityContent, namespaceContent, listOfNamespacesAux, listOfNamespaces, Generate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger checks --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Don Syme <dsyme@users.noreply.github.com>
1 parent 5a29d4b commit 8b7b708

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

src/FSharp.Formatting.ApiDocs/GenerateMarkdown.fs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ let urlEncode (x: string) = HttpUtility.UrlEncode x
1919
/// Returns the trimmed HTML text of an <see cref="T:FSharp.Formatting.ApiDocs.ApiDocHtml"/> value.
2020
let htmlString (x: ApiDocHtml) = (x.HtmlText.Trim())
2121

22+
/// Returns the trimmed HTML text of an <see cref="T:FSharp.Formatting.ApiDocs.ApiDocHtml"/> value,
23+
/// with newlines replaced by <c>&lt;br /&gt;</c> and pipe characters escaped for Markdown tables.
2224
let htmlStringSafe (x: ApiDocHtml) =
2325
(x.HtmlText.Trim()).Replace("\n", "<br />").Replace("|", "&#124;")
2426

27+
/// Wraps an <see cref="T:FSharp.Formatting.ApiDocs.ApiDocHtml"/> value as a Markdown DSL node.
2528
let embed (x: ApiDocHtml) = !!(htmlString x)
29+
/// Wraps an <see cref="T:FSharp.Formatting.ApiDocs.ApiDocHtml"/> value as a Markdown DSL node,
30+
/// escaping characters that would break Markdown table cells.
2631
let embedSafe (x: ApiDocHtml) = !!(htmlStringSafe x)
32+
/// A Markdown DSL node representing an HTML line break.
2733
let br = !!"<br />"
2834

2935
/// Renders Markdown API documentation for all namespaces and entities in an
@@ -34,11 +40,13 @@ type MarkdownRender(model: ApiDocModel, ?menuTemplateFolder: string) =
3440
let collectionName = model.Collection.CollectionName
3541
let qualify = model.Qualify
3642

43+
/// Renders a source-code link icon for a member or entity, returning an empty list when no URL is available.
3744
let sourceLink url =
3845
[ match url with
3946
| None -> ()
4047
| Some href -> link [ img "Link to source code" (sprintf "%scontent/img/github.png" root) ] (href) ]
4148

49+
/// Renders a Markdown section for a group of API members with the given section header.
4250
let renderMembers header (members: ApiDocMember list) =
4351
[ if members.Length > 0 then
4452
``###`` [ !!header ]
@@ -123,6 +131,7 @@ type MarkdownRender(model: ApiDocModel, ?menuTemplateFolder: string) =
123131
if not sl.IsEmpty then
124132
p sl ]
125133

134+
/// Renders a Markdown table listing the given entities (types and modules) with links and summaries.
126135
let renderEntities (entities: ApiDocEntity list) =
127136
[ if entities.Length > 0 then
128137
let hasTypes = entities |> List.exists (fun e -> e.IsTypeDefinition)
@@ -160,6 +169,8 @@ type MarkdownRender(model: ApiDocModel, ?menuTemplateFolder: string) =
160169
[ p [ embedSafe e.Comment.Summary ] ]
161170
[ p [ yield! (sourceLink e.SourceLocation) ] ] ] ] ]
162171

172+
/// Generates the full Markdown content for a single entity (type or module) page,
173+
/// including its summary, members grouped by category, and nested entities.
163174
let entityContent (info: ApiDocEntityInfo) =
164175
// Get all the members & comment for the type
165176
let entity = info.Entity
@@ -319,6 +330,8 @@ type MarkdownRender(model: ApiDocModel, ?menuTemplateFolder: string) =
319330
yield! renderMembers "Instance members" instMembers
320331
yield! renderMembers "Static members" statMembers ]
321332

333+
/// Generates the full Markdown content for a namespace page, including a table of contents
334+
/// and one section per category of entities.
322335
let namespaceContent (nsIndex, ns: ApiDocNamespace) =
323336
let allByCategory = Categorise.entities (nsIndex, ns, false)
324337

@@ -347,6 +360,8 @@ type MarkdownRender(model: ApiDocModel, ?menuTemplateFolder: string) =
347360

348361
yield! renderEntities category.CategoryEntites ]
349362

363+
/// Builds the list-of-namespaces Markdown fragment (for sidebar navigation or index page).
364+
/// When <paramref name="nav"/> is <c>true</c> the active namespace is expanded to show its entities.
350365
let listOfNamespacesAux otherDocs nav (nsOpt: ApiDocNamespace option) =
351366
[
352367
// For FSharp.Core we make all entries available to other docs else there's not a lot else to show.
@@ -395,6 +410,7 @@ type MarkdownRender(model: ApiDocModel, ?menuTemplateFolder: string) =
395410
(e.Url(root, collectionName, qualify, model.FileExtensions.InUrl)) ] ] ]
396411
| _ -> () ]
397412

413+
/// Returns the list-of-namespaces string, using a menu template when available.
398414
let listOfNamespaces otherDocs nav (nsOpt: ApiDocNamespace option) =
399415
let noTemplatingFallback () =
400416
listOfNamespacesAux otherDocs nav nsOpt
@@ -442,6 +458,8 @@ type MarkdownRender(model: ApiDocModel, ?menuTemplateFolder: string) =
442458
let toc = listOfNamespaces true true None
443459
[ yield (ParamKeys.``fsdocs-list-of-namespaces``, toc) ]
444460

461+
/// Writes all API documentation Markdown files (index, one per namespace, one per entity)
462+
/// to <paramref name="outDir"/>, applying <paramref name="templateOpt"/> to each page.
445463
member _.Generate(outDir: string, templateOpt, collectionName, globalParameters) =
446464

447465
let getSubstitutons parameters toc (content: MarkdownDocument) pageTitle =

src/FSharp.Formatting.ApiDocs/XmlDocReader.fs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ open FSharp.Patterns
1616
/// XML documentation comment reading and combining utilities.
1717
[<AutoOpen>]
1818
module internal XmlDocReader =
19+
/// Normalises leading whitespace from a multi-line XML doc comment string, removing
20+
/// the common indentation prefix that .NET compilers emit.
1921
let removeSpaces (comment: string) =
2022
use reader = new StringReader(comment)
2123

@@ -28,6 +30,10 @@ module internal XmlDocReader =
2830

2931
String.removeSpaces lines
3032

33+
/// Converts a Markdown-style literate document (used when a doc comment was written as
34+
/// free Markdown text) into an <see cref="T:FSharp.Formatting.ApiDocs.ApiDocComment"/>.
35+
/// Sections headed with <c>## Returns</c>, <c>## Examples</c>, <c>## Notes</c>, and
36+
/// <c>## Remarks</c> are mapped to the corresponding ApiDocComment fields.
3137
let readMarkdownCommentAsHtml el (doc: LiterateDocument) =
3238
let groups = System.Collections.Generic.List<(_ * _)>()
3339

@@ -107,6 +113,8 @@ module internal XmlDocReader =
107113
rawData = raw
108114
)
109115

116+
/// Tries to parse a <c>[key:value]</c> command embedded inside an XML doc text node.
117+
/// Returns <c>Some (key, value)</c> on success or <c>None</c> if the text is not a command.
110118
let findCommand cmd =
111119
match cmd with
112120
| StringPosition.StartsWithWrapped ("[", "]") (ParseCommand(k, v), _rest) -> Some(k, v)
@@ -137,6 +145,11 @@ module internal XmlDocReader =
137145
else
138146
$"<pre>%s{trimmed}</pre>"
139147

148+
/// Recursively converts an <see cref="T:System.Xml.Linq.XElement"/> to an HTML string,
149+
/// resolving <c>&lt;see cref="…"/&gt;</c> references via <paramref name="urlMap"/> and
150+
/// collecting any embedded <c>[key:value]</c> commands into <paramref name="cmds"/>.
151+
/// When <paramref name="anyTagsOK"/> is <c>true</c>, unknown XML elements are passed
152+
/// through as raw HTML; otherwise they are silently dropped.
140153
let rec readXmlElementAsHtml
141154
anyTagsOK
142155
(urlMap: CrossReferenceResolver)
@@ -217,12 +230,20 @@ module internal XmlDocReader =
217230
let elemAsXml = elem.ToString()
218231
html.Append(elemAsXml) |> ignore
219232

233+
/// Active pattern that matches a <c>&lt;summary&gt;</c> element that contains only text
234+
/// (no child elements). Used to take a fast path that avoids the full HTML builder.
220235
let (|SummaryWithoutChildren|_|) (e: XElement) =
221236
if e.Name.LocalName = "summary" && not e.HasElements then
222237
Some e
223238
else
224239
None
225240

241+
/// Core function that processes a standard C#/F# XML documentation element (the root
242+
/// <c>&lt;member …&gt;</c> or <c>&lt;doc&gt;</c> element) into an
243+
/// <see cref="T:FSharp.Formatting.ApiDocs.ApiDocComment"/> plus an optional list of
244+
/// <c>&lt;namespacedoc&gt;</c> sub-elements (for namespace-level summaries).
245+
/// When <paramref name="summaryExpected"/> is <c>true</c>, a missing <c>&lt;summary&gt;</c>
246+
/// child is treated as raw HTML content.
226247
let readXmlCommentAsHtmlAux
227248
summaryExpected
228249
(urlMap: CrossReferenceResolver)
@@ -409,15 +430,22 @@ module internal XmlDocReader =
409430

410431
comment, nsels
411432

433+
/// Concatenates the HTML text of two <see cref="T:FSharp.Formatting.ApiDocs.ApiDocHtml"/> values,
434+
/// separated by a newline.
412435
let combineHtml (h1: ApiDocHtml) (h2: ApiDocHtml) =
413436
ApiDocHtml(String.concat "\n" [ h1.HtmlText; h2.HtmlText ], None)
414437

438+
/// Combines two optional <see cref="T:FSharp.Formatting.ApiDocs.ApiDocHtml"/> values:
439+
/// returns the non-<c>None</c> side, or concatenates both when both are present.
415440
let combineHtmlOptions (h1: ApiDocHtml option) (h2: ApiDocHtml option) =
416441
match h1, h2 with
417442
| x, None -> x
418443
| None, x -> x
419444
| Some x, Some y -> Some(combineHtml x y)
420445

446+
/// Merges two <see cref="T:FSharp.Formatting.ApiDocs.ApiDocComment"/> values by
447+
/// concatenating their HTML sections (summary, remarks, parameters, examples, etc.).
448+
/// Used when a symbol has documentation spread across multiple XML doc elements.
421449
let combineComments (c1: ApiDocComment) (c2: ApiDocComment) =
422450
ApiDocComment(
423451
xmldoc =
@@ -434,19 +462,28 @@ module internal XmlDocReader =
434462
rawData = c1.RawData @ c2.RawData
435463
)
436464

465+
/// Reduces a list of optional <see cref="T:FSharp.Formatting.ApiDocs.ApiDocComment"/> values
466+
/// (namespace-level doc fragments) by combining all non-<c>None</c> entries into one.
467+
/// Returns <c>None</c> when the list is empty or all entries are <c>None</c>.
437468
let combineNamespaceDocs nspDocs =
438469
nspDocs
439470
|> List.choose id
440471
|> function
441472
| [] -> None
442473
| xs -> Some(List.reduce combineComments xs)
443474

475+
/// Top-level XML doc reader: parses a <c>&lt;member&gt;</c> element (or similar) into an
476+
/// <see cref="T:FSharp.Formatting.ApiDocs.ApiDocComment"/> and a separate namespace-doc
477+
/// comment extracted from any embedded <c>&lt;namespacedoc&gt;</c> elements.
444478
let rec readXmlCommentAsHtml (urlMap: CrossReferenceResolver) (doc: XElement) (cmds: IDictionary<_, _>) =
445479
let doc, nsels = readXmlCommentAsHtmlAux true urlMap doc cmds
446480

447481
let nsdocs = readNamespaceDocs urlMap nsels
448482
doc, nsdocs
449483

484+
/// Reads the contents of <c>&lt;namespacedoc&gt;</c> elements into a combined namespace
485+
/// <see cref="T:FSharp.Formatting.ApiDocs.ApiDocComment"/>, or returns <c>None</c> when
486+
/// the list is empty.
450487
and readNamespaceDocs (urlMap: CrossReferenceResolver) (nsels: XElement list option) =
451488
let nscmds = Dictionary() :> IDictionary<_, _>
452489

0 commit comments

Comments
 (0)