Code formatting
This page demonstrates how to use FSharp.Formatting.CodeFormat
to tokenize
F# source code, obtain information about the source code (mainly tooltips
from the type-checker) and how to turn the code into a nicely formatted HTML.
First, we need to load the assembly and open the necessary namespaces:
open FSharp.Formatting.CodeFormat
open System.Reflection
If you want to process multiple snippets, it is a good idea to keep the formatting agent around if possible. The agent needs to load the F# compiler (which needs to load various files itself) and so this takes a long time.
Processing F# source
The formatting agent provides a CodeFormatAgent.ParseAndCheckSource method (together with an asynchronous
version for use from F# and also a version that returns a .NET Task
for C#).
To call the method, we define a simple F# code as a string:
let source =
"""
let hello () =
printfn "Hello world"
"""
let snippets, diagnostics =
CodeFormatter.ParseAndCheckSource("C:\\snippet.fsx", source, None, None, ignore)
When calling the method, you need to specify a file name and the actual content
of the script file. The file does not have to physically exist. It is used by the
F# compiler to resolve relative references (e.g. #r
) and to automatically name
the module including all code in the file.
You can also specify additional parameters, such as *.dll
references, by passing
a third argument with compiler options (e.g. "-r:Foo.dll -r:Bar.dll"
).
This operation might take some time, so it is a good idea to use an asynchronous variant of the method. It returns two arrays - the first contains F# snippets in the source code and the second contains any errors reported by the compiler. A single source file can include multiple snippets using the same formatting tags as those used on fssnip.net as documented in the about page.
Working with returned tokens
Each returned snippet is essentially just a collection of lines, and each line consists of a sequence of tokens. The following snippet prints basic information about the tokens of our sample snippet:
// Get the first snippet and obtain list of lines
let (Snippet (title, lines)) = snippets |> Seq.head
// Iterate over all lines and all tokens on each line
for (Line (_, tokens)) in lines do
for token in tokens do
match token with
| TokenSpan.Token (kind, code, tip) ->
printf "%s" code
tip
|> Option.iter (fun spans -> printfn "%A" spans)
| TokenSpan.Omitted _
| TokenSpan.Output _
| TokenSpan.Error _ -> ()
printfn ""
The TokenSpan.Token
is the most important kind of token. It consists of a kind
(identifier, keyword, etc.), the original F# code and tooltip information.
The tooltip is further formatted using a simple document format, but we simply
print the value using the F# pretty printing, so the result looks as follows:
let hello[Literal "val hello : unit -> unit"; ...] () =
printfn[Literal "val printfn : TextWriterFormat<'T> -> 'T"; ...] "Hello world"
The Omitted
token is generated if you use the special (*[omit:...]*)
command.
The Output
token is generated if you use the // [fsi:...]
command to format
output returned by F# interactive. The Error
command wraps code that should be
underlined with a red squiggle if the code contains an error.
Generating HTML output
Finally, the CodeFormat
type also includes a method CodeFormat.FormatHtml that can be used
to generate nice HTML output from an F# snippet. This is used, for example, on
F# Snippets. The following example shows how to call it:
let prefix = "fst"
let html = CodeFormat.FormatHtml(snippets, prefix)
// Print all snippets, in case there is more of them
for snip in html.Snippets do
printfn "%s" snip.Content
// Print HTML code that is generated for ToolTips
printfn "%s" html.ToolTip
If the input contains multiple snippets separated using the //[snippet:...]
comment, e.g.:
1: 2: 3: 4: 5: 6: 7: |
// [snippet: First sample] printf "The answer is: %A" 42 // [/snippet] // [snippet: Second sample] printf "Hello world!" // [/snippet] |
then the formatter returns multiple HTML blocks. However, the generated tooltips are shared by all snippets (to save space) and so they are returned separately.
namespace FSharp
--------------------
namespace Microsoft.FSharp
<summary> Uses agent to handle formatting requests </summary>
<summary> Parse, check and annotate the source code specified by 'source', assuming that it is located in a specified 'file'. Optional arguments can be used to give compiler command line options and preprocessor definitions </summary>
union case Snippet.Snippet: title: string * lines: Line list -> Snippet
--------------------
type Snippet = | Snippet of title: string * lines: Line list
<summary> An F# snippet consists of a snippet title and a list of lines </summary>
union case Line.Line: originalLine: string * tokenSpans: TokenSpans -> Line
--------------------
type Line = | Line of originalLine: string * tokenSpans: TokenSpans
<summary> Represents a line of source code as a list of TokenSpan values. This is a single case discriminated union with Line constructor. </summary>
<summary> A token in a parsed F# code snippet. Aside from standard tokens reported from the compiler (Token), this also includes Error (wrapping the underlined tokens), Omitted for the special [omit:...] tags and Output for the special [output:...] tag </summary>
type LiteralAttribute = inherit Attribute new: unit -> LiteralAttribute
--------------------
new: unit -> LiteralAttribute
<summary> Exposes functionality of the F# code formatter with a nice interface </summary>
<summary> Returns the processed snippets as an array </summary>
<summary> Returns the formatted content code for the snipet </summary>
<summary> Returns string with ToolTip elements for all the snippets </summary>