fsprojects / FSharp.Formatting

F# tools for generating documentation (Markdown processor and F# code formatter)
https://fsprojects.github.io/FSharp.Formatting/
Other
462 stars 155 forks source link

API Doc Generation is not Formatting Examples Correctly #761

Closed yazeedobaid closed 1 year ago

yazeedobaid commented 1 year ago

I'm trying to generate API docs for a list of assemblies. The modules, types, and members from these assemblies have examples above them. The examples are written as follows:

/// <summary>
/// A test module...
/// </summary>
/// <example>
/// <code lang="fsharp">
///         let files =
///                [ "build"; "docs" ]
///                |> List.map (fun n -> sprintf "%s.zip" n)
/// </code>
/// </example>
module Test = 
    // the rest of the code...
    ignore

The examples follow the format from F# Recommended XML doc extensions for F# documentation tooling RFC.

However, when I call the dotnet fsdocs build I got the following output in the generated HTML pages:

<div class="fsdocs-xmldoc">
    <div>
      <p class="fsdocs-summary">

 A test module...

      </p>
    </div>
    <h5 class="fsdocs-example-header">
      Example
    </h5>
    <p class="fsdocs-example">
      </p><pre>         let files =
                [ "build"; "docs" ]
                |&gt; List.map (fun n -&gt; sprintf "%s.zip" n)</pre>
    <p></p>
  </div>

Note the code sample is taken as is and put inside a pre element, this will have no syntax highlight or formatting

On the other hand, if I enabled the <UsesMarkdownComments>true</UsesMarkdownComments> in fsproj files and re-write the example above in the following way:

/// <summary>
/// A test module...
/// <example>
/// <code lang="fsharp">
///         let files =
///                [ "build"; "docs" ]
///                |> List.map (fun n -> sprintf "%s.zip" n)
/// </code>
/// </example>
/// </summary>

Note that summary section now contains the whole XML doc, including example. If I kept it as first case, then example will not be read by parser and will be skipped, so it will not make it to the generated HTML

The resulting HTML will be like:

<div>
      <p class="fsdocs-summary">
        </p><p>A test module...</p>
<table class="pre"><tbody><tr><td class="lines"><pre class="fssnip"><span class="l">1: </span>
<span class="l">2: </span>
<span class="l">3: </span>
</pre></td>
<td class="snippet"><pre class="fssnip highlighted"><code lang="fsharp">    <span class="k">let</span> <span onmouseout="hideTip(event, 'fs1', 1)" onmouseover="showTip(event, 'fs1', 1)" class="id">files</span> <span class="o">=</span>
           <span class="pn">[</span> <span class="s">"build"</span><span class="pn">;</span> <span class="s">"docs"</span> <span class="pn">]</span>
           <span class="o">|&gt;</span> <span onmouseout="hideTip(event, 'fs2', 2)" onmouseover="showTip(event, 'fs2', 2)" class="m">List</span><span class="pn">.</span><span onmouseout="hideTip(event, 'fs3', 3)" onmouseover="showTip(event, 'fs3', 3)" class="id">map</span> <span class="pn">(</span><span class="k">fun</span> <span onmouseout="hideTip(event, 'fs4', 4)" onmouseover="showTip(event, 'fs4', 4)" class="fn">n</span> <span class="k">-&gt;</span> <span onmouseout="hideTip(event, 'fs5', 5)" onmouseover="showTip(event, 'fs5', 5)" class="fn">sprintf</span> <span class="s">"</span><span class="pf">%s</span><span class="s">.zip"</span> <span onmouseout="hideTip(event, 'fs4', 6)" onmouseover="showTip(event, 'fs4', 6)" class="fn">n</span><span class="pn">)</span>
</code></pre></td>
</tr>
</tbody></table>
<div class="fsdocs-tip" id="fs1">val files: string list</div>
<div class="fsdocs-tip" id="fs2">Multiple items<br>module List

from Microsoft.FSharp.Collections<br><em>&lt;summary&gt;Contains operations for working with values of type &lt;see cref="T:Microsoft.FSharp.Collections.list`1" /&gt;.&lt;/summary&gt;<br>&lt;namespacedoc&gt;&lt;summary&gt;Operations for collections such as lists, arrays, sets, maps and sequences. See also 
    &lt;a href="https://docs.microsoft.com/dotnet/fsharp/language-reference/fsharp-collection-types"&gt;F# Collection Types&lt;/a&gt; in the F# Language Guide.
 &lt;/summary&gt;&lt;/namespacedoc&gt;</em><br><br>--------------------<br>type List&lt;'T&gt; =
  | op_Nil
  | op_ColonColon of Head: 'T * Tail: 'T list
  interface IReadOnlyList&lt;'T&gt;
  interface IReadOnlyCollection&lt;'T&gt;
  interface IEnumerable
  interface IEnumerable&lt;'T&gt;
  member GetReverseIndex: rank: int * offset: int -&gt; int
  member GetSlice: startIndex: int option * endIndex: int option -&gt; 'T list
  static member Cons: head: 'T * tail: 'T list -&gt; 'T list
  member Head: 'T
  member IsEmpty: bool
  member Item: index: int -&gt; 'T with get
  ...<br><em>&lt;summary&gt;The type of immutable singly-linked lists.&lt;/summary&gt;<br>&lt;remarks&gt;Use the constructors &lt;c&gt;[]&lt;/c&gt; and &lt;c&gt;::&lt;/c&gt; (infix) to create values of this type, or
 the notation &lt;c&gt;[1;2;3]&lt;/c&gt;. Use the values in the &lt;c&gt;List&lt;/c&gt; module to manipulate 
 values of this type, or pattern match against the values directly.
 &lt;/remarks&gt;<br>&lt;exclude /&gt;</em></div>
<div class="fsdocs-tip" id="fs3">val map: mapping: ('T -&gt; 'U) -&gt; list: 'T list -&gt; 'U list<br><em>&lt;summary&gt;Builds a new collection whose elements are the results of applying the given function
 to each of the elements of the collection.&lt;/summary&gt;<br>&lt;param name="mapping"&gt;The function to transform elements from the input list.&lt;/param&gt;<br>&lt;param name="list"&gt;The input list.&lt;/param&gt;<br>&lt;returns&gt;The list of transformed elements.&lt;/returns&gt;<br>&lt;example id="map-1"&gt;&lt;code lang="fsharp"&gt;
 let inputs = [ "a"; "bbb"; "cc" ]

 inputs |&amp;gt; List.map (fun x -&amp;gt; x.Length)
 &lt;/code&gt;
 Evaluates to &lt;c&gt;[ 1; 3; 2 ]&lt;/c&gt;&lt;/example&gt;</em></div>
<div class="fsdocs-tip" id="fs4" style="position: absolute; left: 1116px; top: 392px; display: none;">val n: string</div>
<div class="fsdocs-tip" id="fs5">val sprintf: format: Printf.StringFormat&lt;'T&gt; -&gt; 'T<br><em>&lt;summary&gt;Print to a string using the given format.&lt;/summary&gt;<br>&lt;param name="format"&gt;The formatter.&lt;/param&gt;<br>&lt;returns&gt;The formatted result.&lt;/returns&gt;<br>&lt;example&gt;See &lt;c&gt;Printf.sprintf&lt;/c&gt; (link: &lt;see cref="M:Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThen``1" /&gt;) for examples.&lt;/example&gt;</em></div>

      <p></p>
    </div>

The HTML got syntax highlight and formatting.

This way as far as I know uses the Markdown parser, however, it violates the F# recommentation for XML documentation and IDE doesn't understand this format.

Is there a way to get syntax highlight and formatting using the first way? Or am I missing sth or an argument to fsdocs tool to enable it?

Thanks

nhirschey commented 1 year ago

Your first way is correct as far as I know, no highlighting. See for example F# core docs generated by fsdocs at this link for array, they don’t have highlighting.

Agreed though, the full highlighting features would be nice for API docs output.

yazeedobaid commented 1 year ago

Yes, that is what I think, the first way is the correct one. But I was playing with this to see what will work and what will not. Also thanks for sending the sample link. I can propose a simple solution for this, so other syntax highlighting libraries like highlightjs and prismjs assume that code samples are in a pre element with a code element inside it. As follows:

<pre>
    <code class="language-fsharp">
        let files =
            [ "build"; "docs" ]
            |> List.map (fun n -> sprintf "%s.zip" n)
    </code>
</pre>

Also, w3.org site has its samples written this way, so it seems this is the accessible way of writing code samples. I can send a PR to handle this in GenerateModel class to insert a code element in the pre element and map the lang=fsharp attribute on XML documentation to a class name for syntax highlighters to get. In this way, users can pull a syntax highlighter and it will work out of the box.

By this, the XML documentation will be kept with no changes. However, the output HTML will be as follows with changes highlighted.

<pre>
+    <code class="fsharp language-fsharp">
        let files =
            [ "build"; "docs" ]
            |> List.map (fun n -> sprintf "%s.zip" n)
+    </code>
</pre>