Header menu logo fantomas

Recipes

Fantomas has a limited set of settings and adheres to style guides. These style guides are meant to be agnostic to any specific framework.

That being said, there are certain combinations that provide a more ideal result when working with certain scenarios.

These snippets aren’t to spark requests for new settings suited to framework of the day!

Remember that a .editorconfig file can apply certain settings only to certain files. Sometimes, it makes sense to tweak a few setting for a subset of your codebase.

HTML DSL

When working with a HTML inspired DSL (typically a function call with one or two lists), you can use fsharp_experimental_elmish

Fable.React

Elmish

formatCode
    """
div
    []
    [
        h1 [] [ str "Some title" ]
        ul
            []
            [
                for p in model.Points do
                    li [] [ str $"%i{p.X}, %i{p.Y}" ]
            ]
        hr []
    ]
    """
    """
fsharp_experimental_elmish = true
    """
div [] [
    h1 [] [ str "Some title" ]
    ul [] [
        for p in model.Points do
            li [] [ str $"%i{p.X}, %i{p.Y}" ]
    ]
    hr []
]

Feliz

formatCode
    """
Html.div
    [
        Html.h1 [ str "Some title" ]
        Html.ul
            [
                for p in model.Points do
                    Html.li [ str $"%i{p.X}, %i{p.Y}" ]
            ]
    ]
    """
    """
fsharp_experimental_elmish = true
    """
Html.div [
    Html.h1 [ str "Some title" ]
    Html.ul [
        for p in model.Points do
            Html.li [ str $"%i{p.X}, %i{p.Y}" ]
    ]
]

Giraffe

formatCode
    """
let indexView =
    html
        []
        [ head [] [ title [] [ str "Giraffe Sample" ] ]
          body
              []
              [ h1 [] [ str "I |> F#" ]
                p [ _class "some-css-class"; _id "someId" ] [ str "Hello World" ] ] ]
    """
    """
fsharp_experimental_elmish = true
    """
let indexView =
    html [] [
        head [] [ title [] [ str "Giraffe Sample" ] ]
        body [] [
            h1 [] [ str "I |> F#" ]
            p [ _class "some-css-class"; _id "someId" ] [ str "Hello World" ]
        ]
    ]

Falco

formatCode
    """
let markup =
    Elem.div
        []
        [ Text.comment "An HTML comment"
          Elem.p [] [ Text.raw "A paragraph" ]
          Elem.p [] [ Text.rawf "Hello %s" "Jim" ]
          Elem.code [] [ Text.enc "<div>Hello</div>" ] ] // HTML encodes text before rendering
    """
    """
fsharp_experimental_elmish = true
    """
let markup =
    Elem.div [] [
        Text.comment "An HTML comment"
        Elem.p [] [ Text.raw "A paragraph" ]
        Elem.p [] [ Text.rawf "Hello %s" "Jim" ]
        Elem.code [] [ Text.enc "<div>Hello</div>" ]
    ] // HTML encodes text before rendering

Expecto

The Expect test framework can benefit from Stroustrup style when defining test lists.

formatCode
    """
let tests =
    testList
        "A test group"
        [ test "one test" { Expect.equal (2 + 2) 4 "2+2" }

          test "another test that fails" { Expect.equal (3 + 3) 5 "3+3" }

          testAsync "this is an async test" {
              let! x = async { return 4 }
              Expect.equal x (2 + 2) "2+2"
          }

          testTask "this is a task test" {
              let! n = Task.FromResult 2
              Expect.equal n 2 "n=2"
          } ]
    |> testLabel "samples"
    """
    """
fsharp_multiline_bracket_style = stroustrup
    """
let tests =
    testList "A test group" [
        test "one test" { Expect.equal (2 + 2) 4 "2+2" }

        test "another test that fails" { Expect.equal (3 + 3) 5 "3+3" }

        testAsync "this is an async test" {
            let! x = async { return 4 }
            Expect.equal x (2 + 2) "2+2"
        }

        testTask "this is a task test" {
            let! n = Task.FromResult 2
            Expect.equal n 2 "n=2"
        }
    ]
    |> testLabel "samples"

FAKE

At the end of a FAKE script, the target dependencies are typically listed using custom operators.
Using fsharp_max_infix_operator_expression you can tweak when they should go to the next line.

formatCode
    """
"Build"
==> "EnsureCanScaffoldCodeFix"
==> "LspTest"
==> "Coverage"
==> "Test"
==> "All"

"Clean" ==> "LocalRelease" ==> "ReleaseArchive" ==> "Release"
    """
    """
fsharp_max_infix_operator_expression = 5
    """
"Build"
==> "EnsureCanScaffoldCodeFix"
==> "LspTest"
==> "Coverage"
==> "Test"
==> "All"

"Clean"
==> "LocalRelease"
==> "ReleaseArchive"
==> "Release"

Paragraphs

When expressions are multiline Fantomas will put a blank line before and after them. This helps for code to stay consistent in teams.

formatCode
    """
let Foo =
    try // Start paragraph 1
        printfn "%A" blah
    with ex ->
        printfn "Failed to print blah" // End paragraph 1

    let mutable a = 8 // This line belongs // Start paragraph 2
    for i in [ 1 .. 10 ] do
        a <- a + i // Start paragraph 2
    """
    ""
let Foo =
    try // Start paragraph 1
        printfn "%A" blah
    with ex ->
        printfn "Failed to print blah" // End paragraph 1

    let mutable a = 8 // This line belongs // Start paragraph 2

    for i in [ 1..10 ] do
        a <- a + i // Start paragraph 2

However, some developers like to group their code in self-defined paragraphs, and thus want full control when Fantomas inserts blank lines. Full control is not possible but outside top level expressions this can be done via fsharp_blank_lines_around_nested_multiline_expressions = false.

⚠️ This approach is generally advised against because it gives the code author control over newlines. Within teams, this can lead to debates, which is precisely what using Fantomas aims to eliminate: code style discussions. ⚠️

formatCode
    """
let Foo =
    try // Start paragraph 1
        printfn "%A" blah
    with ex ->
        printfn "Failed to print blah" // End paragraph 1

    let mutable a = 8 // This line belongs // Start paragraph 2
    for i in [ 1 .. 10 ] do
        a <- a + i // Start paragraph 2
    """
    """
fsharp_blank_lines_around_nested_multiline_expressions = false
    """
let Foo =
    try // Start paragraph 1
        printfn "%A" blah
    with ex ->
        printfn "Failed to print blah" // End paragraph 1

    let mutable a = 8 // This line belongs // Start paragraph 2
    for i in [ 1..10 ] do
        a <- a + i // Start paragraph 2

Early returns

In F# there is no such concept as early returns. However, sometimes we do want to return an empty or error result when a certain condition is not met. To avoid extreme indentation in our happy path, you can use fsharp_experimental_keep_indent_in_branch = true.

formatCode
    """
let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix =
    Run.ifDiagnosticByCode (set [ "20" ]) (fun diagnostic (codeActionParams: CodeActionParams) ->
        asyncResult {
            if mDiag.StartLine <> mDiag.EndLine then
                // Only do single line for now
                return []
            else

            // Happy path at same indent

            let! (parseAndCheckResults: ParseAndCheckResults, _line: string, sourceText: IFSACSourceText) =
                getParseResultsForFile fileName fcsPos

            let mExprOpt =
                (fcsPos, parseAndCheckResults.GetParseResults.ParseTree)
                ||> ParsedInput.tryPick (fun path node ->
                    match node with
                    | SyntaxNode.SynExpr(e) when Range.equals mDiag e.Range -> Some(path, e)
                    | _ -> None)

            match mExprOpt with
            | None ->
                // Empty result is Option is None
                return []
            | Some(path, expr) ->

            // Happy path at same indent

            return
                [ { SourceDiagnostic = None
                    Title = title
                    File = codeActionParams.TextDocument
                    Edits =
                        [| { Range = fcsRangeToLsp expr.Range
                            NewText = newText } |]
                    Kind = FixKind.Fix } ]
        })

    """
    """
fsharp_experimental_keep_indent_in_branch = true
    """
let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix =
    Run.ifDiagnosticByCode (set [ "20" ]) (fun diagnostic (codeActionParams: CodeActionParams) ->
        asyncResult {
            if mDiag.StartLine <> mDiag.EndLine then
                // Only do single line for now
                return []
            else

            // Happy path at same indent

            let! (parseAndCheckResults: ParseAndCheckResults, _line: string, sourceText: IFSACSourceText) =
                getParseResultsForFile fileName fcsPos

            let mExprOpt =
                (fcsPos, parseAndCheckResults.GetParseResults.ParseTree)
                ||> ParsedInput.tryPick (fun path node ->
                    match node with
                    | SyntaxNode.SynExpr(e) when Range.equals mDiag e.Range -> Some(path, e)
                    | _ -> None)

            match mExprOpt with
            | None ->
                // Empty result is Option is None
                return []
            | Some(path, expr) ->

            // Happy path at same indent

            return
                [ { SourceDiagnostic = None
                    Title = title
                    File = codeActionParams.TextDocument
                    Edits = [| { Range = fcsRangeToLsp expr.Range NewText = newText } |]
                    Kind = FixKind.Fix } ]
        })

⚠️ The downside of this setting is that it only respects this style of formatting if it was already present in the original source. The problem with this approach is that the author of the original code decides whether this style is used. Discuss this with your team! ⚠️

namespace System
namespace Fantomas
namespace Fantomas.Core
module EditorConfig from Fantomas
val formatCode: input: string -> settings: string -> unit
val input: string
val settings: string
Multiple items
val string: value: 'T -> string

--------------------
type string = String
val async: AsyncBuilder
val config: FormatConfig
val editorConfigProperties: Collections.Generic.IReadOnlyDictionary<string,string>
String.Split([<ParamArray>] separator: char array) : string array
String.Split(separator: string array, options: StringSplitOptions) : string array
String.Split(separator: string, ?options: StringSplitOptions) : string array
String.Split(separator: char array, options: StringSplitOptions) : string array
String.Split(separator: char array, count: int) : string array
String.Split(separator: char, ?options: StringSplitOptions) : string array
String.Split(separator: string array, count: int, options: StringSplitOptions) : string array
String.Split(separator: string, count: int, ?options: StringSplitOptions) : string array
String.Split(separator: char array, count: int, options: StringSplitOptions) : string array
String.Split(separator: char, count: int, ?options: StringSplitOptions) : string array
[<Struct>] type StringSplitOptions = | None = 0 | RemoveEmptyEntries = 1 | TrimEntries = 2
<summary>Specifies options for applicable <see cref="Overload:System.String.Split" /> method overloads, such as whether to omit empty substrings from the returned array or trim whitespace from substrings.</summary>
field StringSplitOptions.RemoveEmptyEntries: StringSplitOptions = 1
type Array = interface ICollection interface IEnumerable interface IList interface IStructuralComparable interface IStructuralEquatable interface ICloneable member Clone: unit -> obj member CopyTo: array: Array * index: int -> unit + 1 overload member GetEnumerator: unit -> IEnumerator member GetLength: dimension: int -> int ...
<summary>Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the base class for all arrays in the common language runtime.</summary>
val choose: chooser: ('T -> 'U option) -> array: 'T array -> 'U array
val line: string
val parts: string array
field StringSplitOptions.TrimEntries: StringSplitOptions = 2
property Array.Length: int with get
<summary>Gets the total number of elements in all the dimensions of the <see cref="T:System.Array" />.</summary>
<exception cref="T:System.OverflowException">The array is multidimensional and contains more than <see cref="F:System.Int32.MaxValue">Int32.MaxValue</see> elements.</exception>
<returns>The total number of elements in all the dimensions of the <see cref="T:System.Array" />; zero if there are no elements in the array.</returns>
union case Option.None: Option<'T>
union case Option.Some: Value: 'T -> Option<'T>
val readOnlyDict: keyValuePairs: ('Key * 'Value) seq -> Collections.Generic.IReadOnlyDictionary<'Key,'Value> (requires equality)
val parseOptionsFromEditorConfig: fallbackConfig: FormatConfig -> editorConfigProperties: Collections.Generic.IReadOnlyDictionary<string,string> -> FormatConfig
type FormatConfig = { IndentSize: Num MaxLineLength: Num EndOfLine: EndOfLineStyle InsertFinalNewline: bool SpaceBeforeParameter: bool SpaceBeforeLowercaseInvocation: bool SpaceBeforeUppercaseInvocation: bool SpaceBeforeClassConstructor: bool SpaceBeforeMember: bool SpaceBeforeColon: bool ... } member IsStroustrupStyle: bool static member Default: FormatConfig
property FormatConfig.Default: FormatConfig with get
val result: FormatResult
type CodeFormatter = static member FormatASTAsync: ast: ParsedInput -> Async<string> + 2 overloads static member FormatDocumentAsync: isSignature: bool * source: string -> Async<FormatResult> + 2 overloads static member FormatOakAsync: oak: Oak -> Async<string> + 1 overload static member FormatSelectionAsync: isSignature: bool * source: string * selection: range -> Async<string * range> + 1 overload static member GetVersion: unit -> string static member IsValidFSharpCodeAsync: isSignature: bool * source: string -> Async<bool> static member MakePosition: line: int * column: int -> pos static member MakeRange: fileName: string * startLine: int * startCol: int * endLine: int * endCol: int -> range static member ParseAsync: isSignature: bool * source: string -> Async<(ParsedInput * string list) array> static member ParseOakAsync: isSignature: bool * source: string -> Async<(Oak * string list) array> ...
static member CodeFormatter.FormatDocumentAsync: isSignature: bool * source: string -> Async<FormatResult>
static member CodeFormatter.FormatDocumentAsync: isSignature: bool * source: string * config: FormatConfig -> Async<FormatResult>
static member CodeFormatter.FormatDocumentAsync: isSignature: bool * source: string * config: FormatConfig * cursor: Fantomas.FCS.Text.pos -> Async<FormatResult>
val printf: format: Printf.TextWriterFormat<'T> -> 'T
FormatResult.Code: string
<summary> Formatted code </summary>
Multiple items
module Async from Fantomas.Core

--------------------
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit) static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate) static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async<bool> static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async<bool> static member CancelDefaultToken: unit -> unit static member Catch: computation: Async<'T> -> Async<Choice<'T,exn>> static member Choice: computations: Async<'T option> seq -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * obj -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...

--------------------
type Async<'T>
static member Async.RunSynchronously: computation: Async<'T> * ?timeout: int * ?cancellationToken: Threading.CancellationToken -> 'T

Type something to start searching.