Skip to content

Latest commit

 

History

History
1194 lines (866 loc) · 30.4 KB

Documentation.md

File metadata and controls

1194 lines (866 loc) · 30.4 KB

Fantomas: How to use

Using the command line tool


Create a .NET tool manifest to install tools locally

dotnet new tool-manifest

Install the command line tool with:

dotnet tool install fantomas

or install the tool globally with

dotnet tool install -g fantomas

For the overview how to use the tool, you can type the command

dotnet fantomas --help
USAGE: dotnet fantomas [--help] [--recurse] [--force] [--profile] [--out <string>] [--check] [--daemon] [--version] [<string>...]

INPUT:

    <string>...           Input paths: can be multiple folders or files with *.fs,*.fsi,*.fsx,*.ml,*.mli extension.

OPTIONS:

    --recurse, -r         Process the input folder recursively.
    --force               Print the output even if it is not valid F# code. For debugging purposes only.
    --profile             Print performance profiling information.
    --out <string>        Give a valid path for files/folders. Files should have .fs, .fsx, .fsi, .ml or .mli extension only. Multiple files/folders are not supported.
    --check               Don't format files, just check if they have changed. Exits with 0 if it's formatted correctly, with 1 if some files need formatting and 99 if there was an internal error
    --daemon              Daemon mode, launches an LSP-like server to can be used by editor tooling.
    --version, -v         Displays the version of Fantomas
    --help                display this list of options.

You have to specify an input path and optionally an output path. The output path is prompted by --out e.g.

dotnet fantomas ../../../../tests/stackexchange/array.fs --out ../../../../tests/stackexchange_output/array.fs 

Both paths have to be files or folders at the same time. If they are folders, the structure of input folder will be reflected in the output one. The tool will explore the input folder recursively if you set --recurse option. If you omit the output path, Fantomas will overwrite the input files.

Check mode

starting version 3.3

Verify that a single file or folder was formatted correctly.

dotnet fantomas --check Source.fs

This will verify if the file Source.fs still needs formatting. If it does, the process will return exit code 99. In the case that the file does not require any formatting, exit code 0 is returned. Unexpected errors will return exit code 1.

This scenario is meant to be executed in a continuous integration environment, to enforce that the newly added code was formatted correctly.

Multiple paths

starting version 4.5

Multiple paths can be passed as last argument, these can be both files and folders.
This cannot be combined with the --out and --stdout flags.
When combined with the --recurse flag, all passed folders will be processed recursively.

One interesting use-case of passing down multiple paths is that you can easily control the selection and filtering of paths from the current shell.

Consider the following PowerShell scripts:

# Create an array with paths
$files =
     Get-ChildItem src/*.fs -Recurse # Find all *.fs files in src,
     | Where-Object { $_.FullName -notlike "*obj*" } # ignore files in the `obj` folder
     | ForEach-Object { $_.FullName } #  and select the full path name.

& "dotnet" "fantomas" $files
# Filter all added and modified files in git
$files = git status --porcelain | Where-Object { $_ -match "^\s?A?M(.*)\.fs(x|i)?$" } | ForEach-Object { $_.TrimStart("AM").TrimStart(" ", "M") }
& "dotnet" "fantomas" $files

Or usage with find on unix:

find my-project/ -type f -name "*.fs" -not -path "*obj*" | xargs dotnet fantomas --check

Daemon mode

--daemon should not be used directly by end-users. Learn more about this feature in the Daemon mode documentation

Configuration

Fantomas ships with a series of format options. These can be stored in an .editorconfig file and will be picked up automatically by the commandline tool.

A default .editorconfig file would look like

[*.{fs,fsx}]
indent_size=4
max_line_length=120
end_of_line=crlf
insert_final_newline=true
fsharp_space_before_parameter=true
fsharp_space_before_lowercase_invocation=true
fsharp_space_before_uppercase_invocation=false
fsharp_space_before_class_constructor=false
fsharp_space_before_member=false
fsharp_space_before_colon=false
fsharp_space_after_comma=true
fsharp_space_before_semicolon=false
fsharp_space_after_semicolon=true
fsharp_space_around_delimiter=true
fsharp_max_if_then_short_width=0
fsharp_max_if_then_else_short_width=60
fsharp_max_infix_operator_expression=80
fsharp_max_record_width=40
fsharp_max_record_number_of_items=1
fsharp_record_multiline_formatter=character_width
fsharp_max_array_or_list_width=80
fsharp_max_array_or_list_number_of_items=1
fsharp_array_or_list_multiline_formatter=character_width
fsharp_max_value_binding_width=80
fsharp_max_function_binding_width=40
fsharp_max_dot_get_expression_width=80
fsharp_multiline_block_brackets_on_same_column=false
fsharp_newline_between_type_definition_and_members=true
fsharp_align_function_signature_to_indentation=false
fsharp_alternative_long_member_definitions=false
fsharp_multi_line_lambda_closing_newline=false
fsharp_experimental_keep_indent_in_branch=false
fsharp_blank_lines_around_nested_multiline_expressions=true
fsharp_bar_before_discriminated_union_declaration=false
fsharp_experimental_stroustrup_style=false
fsharp_keep_max_number_of_blank_lines=100
fsharp_strict_mode=false

Please note that you should only add settings to the .editorconfig file when you want to deviate from the default settings. Copying the entire list above is unnecessary.

indent_size

indent_size has to be between 1 and 10.

This preference sets the indentation The common values are 2 and 4. The same indentation is ensured to be consistent in a source file. Default = 4.

defaultConfig

let inline selectRandom (f: _ []) =
    let r = random 1.0

    let rec find =
        function
        | 0 -> fst f.[0]
        | n when r < snd f.[n] -> fst f.[n]
        | n -> find (n - 1)

    find <| f.Length - 1

{ defaultConfig with IdentSize = 2 }

let inline selectRandom (f: _ []) =
  let r = random 1.0

  let rec find =
    function
    | 0 -> fst f.[0]
    | n when r < snd f.[n] -> fst f.[n]
    | n -> find (n - 1)

  find <| f.Length - 1

max_line_length

max_line_length has to be an integer greater or equal to 60. This preference sets the column where we break F# constructs into new lines. Default = 120.

defaultConfig

match myValue with
| Some foo -> someLongFunctionNameThatWillTakeFooAndReturnsUnit foo
| None -> printfn "nothing"

{ defaultConfig with MaxLineLength = 60 }

match myValue with
| Some foo ->
    someLongFunctionNameThatWillTakeFooAndReturnsUnit foo
| None -> printfn "nothing"

end_of_line

end_of_line determines the newline character, lf will add \n where crlf will add \r\n. cr is not supported by the F# language spec. If not set by the user, the default value is determined by System.Environment.NewLine.

insert_final_newline

Adds a final newline character at the end of the file. Default = true

defaultConfig

let a = 42

{ default with InsertFinalNewline = false }

let a = 42

fsharp_space_before_parameter

Add a space after the name of a function and before the opening parenthesis of the first parameter. This setting influences function definitions. Default = true.

defaultConfig

let value (a: int) = x
let DumpTrace () = ()

{ defaultConfig with SpaceBeforeParameter = false }

let value(a: int) = x
let DumpTrace() = ()

fsharp_space_before_lowercase_invocation

Add a space after the name of a lowercased function and before the opening parenthesis of the first argument. This setting influences function invocation. Default = true.

defaultConfig

value (a, b)
startTimer ()

{ defaultConfig with SpaceBeforeLowercaseInvocation = false }

value(a, b)
startTimer()

fsharp_space_before_uppercase_invocation

Add a space after the name of a uppercased function and before the opening parenthesis of the first argument. This setting influences function invocation. Default = false.

defaultConfig

Value(a, b)
person.ToString()

{ defaultConfig with SpaceBeforeUppercaseInvocation = true }

Value (a, b)
person.ToString ()

fsharp_space_before_class_constructor

Add a space after a type name and before the class constructor. Default = false.

defaultConfig

type Person() =
    class
    end

{ defaultConfig with SpaceBeforeClassConstructor = true }

type Person () =
    class
    end

fsharp_space_before_member

Add a space after a member name and before the opening parenthesis of the first parameter. Default = false.

defaultConfig

type Person() =
    member this.Walk(distance: int) = ()
    member this.Sleep() = ignore
    member __.singAlong() = ()
    member __.swim(duration: TimeSpan) = ()

{ defaultConfig with SpaceBeforeMember = true }

type Person() =
    member this.Walk (distance: int) = ()
    member this.Sleep () = ignore
    member __.singAlong () = ()
    member __.swim (duration: TimeSpan) = ()

fsharp_space_before_colon

Add a space before :. Please note that not every : is controlled by this setting. Default = false.

defaultConfig

type Point = { x: int; y: int }
let myValue: int = 42 // See https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting#right-pad-value-and-function-argument-type-annotations
let update (msg: Msg) (model: Model) : Model = model // See https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting#surround-return-type-annotations-with-white-space

{ defaultConfig with SpaceBeforeColon = true }

type Point = { x : int; y : int }
let myValue : int = 42
let update (msg : Msg) (model : Model) : Model = model

fsharp_space_after_comma

Adds a space after , in tuples. Default = true.

defaultConfig

myValue.SomeFunction(foo, bar, somethingElse)
(a, b, c)

{ defaultConfig with SpaceAfterComma = false }

myValue.SomeFunction(foo,bar,somethingElse)
(a,b,c)

fsharp_space_before_semicolon

Adds a space before ; in records, arrays, lists, etc. Default = false.

defaultConfig

let a = [ 1; 2; 3 ]
let b = [| foo; bar |]
type C = { X: int; Y: int }

{ defaultConfig with SpaceBeforeSemicolon = true }

let a = [ 1 ; 2 ; 3 ]
let b = [| foo ; bar |]
type C = { X: int ; Y: int }

fsharp_space_after_semicolon

Adds a space after ; in records, arrays, lists, etc. Default = true.

defaultConfig

let a = [ 1; 2; 3 ]
let b = [| foo; bar |]
type C = { X: int; Y: int }

{ defaultConfig with SpaceAfterSemicolon = false }

let a = [ 1;2;3 ]
let b = [| foo;bar |]
type C = { X: int;Y: int }

fsharp_space_around_delimiter

Adds a space around delimiters like [,[|,{. Default = true.

defaultConfig

let a = [ 1;2;3 ]
let b = [| 4;5;6 |]

{ defaultConfig with SpaceAroundDelimiter = false }

let a = [1;2;3]
let b = [|4;5;6|]

fsharp_max_if_then_short_width

Control the maximum length for which if/then expression without an else expression can be on one line.
The Microsoft F# style guide recommends to never write such an expression in one line.

If the else expression is absent, it is recommended to never to write the entire expression in one line. Default = 0.

defaultConfig

if a then 
    ()

{ defaultConfig with MaxIfThenShortWidth = 15 }

if a then ()

fsharp_max_if_then_else_short_width

Fantomas by default follows the if/then/else conventions listed in the Microsoft F# style guide. There is stated:

Indentation of conditionals depends on the size and complexity of the expressions that make them up. Write them on one line when: cond, e1, and e2 are short e1 and e2 are not if/then/else expressions themselves.

This setting facilitates this by determining the maximum character width where the if/then/else expression stays in one line. Default = 60.

defaultConfig

if myCheck then truth else bogus

{ defaultConfig with MaxIfThenElseShortWidth = 10 }

if myCheck then 
    truth 
else 
    bogus

fsharp_max_infix_operator_expression

Control the maximum length for which infix expression can be on one line. Default = 80.

defaultConfig

let WebApp =
    route "/ping" >=> authorized >=> text "pong"

{ defaultConfig with MaxInfixOperatorExpression = 20 }

let WebApp =
    route "/ping"
    >=> authorized
    >=> text "pong"

fsharp_max_record_width

Control the maximum width for which records should be in one line. Default = 40. Requires fsharp_record_multiline_formatter to be character_width to take effect.

defaultConfig

type MyRecord = { X: int; Y: int; Length: int }
let myInstance = { X = 10; Y = 20; Length = 90 }

{ defaultConfig with MaxRecordWidth = 20 }

type MyRecord =
    { X: int
      Y: int
      Length: int }

let myInstance =
    { X = 10
      Y = 20
      Length = 90 }

fsharp_max_record_number_of_items

Control the maximum number of fields for which records should be in one line. Default = 1. Requires fsharp_record_multiline_formatter to be number_of_items to take effect.

defaultConfig

type R = { x: int }

type S = { x: int; y: string }

type T = { x: int; y: string; z: float }

let myRecord = { r = 3 }

let myRecord' = { r with x = 3 }

let myRecord'' = { r with x = 3; y = "hello" }

let myRecord''' = { r with x = 3; y = "hello"; z = 0.0 }

{ defaultConfig with MaxRecordSize = 2; RecordMultilineFormatter = MultilineFormatterType.NumberOfItems }

type R = { x: int }

type S = { x: int; y: string }

type T =
    { x: int
      y: string
      z: float }

let myRecord = { r = 3 }

let myRecord' = { r with x = 3 }

let myRecord'' = { r with x = 3; y = "hello" }

let myRecord''' =
    { r with
          x = 3
          y = "hello"
          z = 0.0 }

fsharp_record_multiline_formatter

Split records expressions/statements into multiple lines based on the given condition. character_width uses character count of the expression, controlled by fsharp_max_record_width. number_of_items uses the number of fields in the record, controlled by fsharp_max_record_number_of_items. Default = character_width. Note that in either case, record expressions/statements are still governed by max_line_length.

defaultConfig

type R = { x: int }

type S = { x: int; y: string }

let myRecord = { r = 3 }

let myRecord' = { r with x = 3 }

let myRecord'' = { r with x = 3; y = "hello" }

{ defaultConfig with RecordMultilineFormatter = MultilineFormatterType.NumberOfItems }

type R = { x: int }

type S =
    { x: int
      y: string }

let myRecord = { x = 3 }

let myRecord' = { r with x = 3 }

let myRecord'' =
    { r with
          x = 3
          y = "hello" }

fsharp_max_array_or_list_width

Control the maximum width for which lists and arrays can be in one line. Default = 80. Requires fsharp_array_or_list_multiline_formatter to be character_width to take effect.

defaultConfig

let myArray = [| one; two; three |]

{ defaultConfig with MaxArrayOrListWidth = 20 }

let myArray =
    [| one
       two
       three |]

fsharp_max_array_or_list_number_of_items

Control the maximum number of elements for which lists and arrays can be in one line. Default = 1. Requires fsharp_array_or_list_multiline_formatter to be number_of_items to take effect.

defaultConfig

let myList = [ one; two ]

let myArray = [| one; two; three |]

{ defaultConfig with MaxArrayOrListNumberOfItems = 2; ArrayOrListMultilineFormatter = MultilineFormatterType.NumberOfItems }

let myList = [ one; two ]

let myArray =
    [| one
       two
       three |]

fsharp_array_or_list_multiline_formatter

Split arrays and lists into multiple lines based on the given condition. character_width uses character count of the expression, controlled by fsharp_max_array_or_list_width. number_of_items uses the number of elements in the array or list, controlled by fsharp_max_array_or_list_number_of_items. Default = character_width. Note that in either case, list expressions are still governed by max_line_length.

defaultConfig

let myArray = [| one; two; three |]

{ defaultConfig with ArrayOrListMultilineFormatter = MultilineFormatterType.NumberOfItems }

let myArray =
    [| one
       two
       three |]

fsharp_max_value_binding_width

Control the maximum expression width for which let and member value/property bindings should be in one line. The width is that of the pattern for the binding plus the implementating expression but not the keywords (e.g. "let"). Default = 80.

defaultConfig

let title = "Great title of project"

type MyType() =
    member this.HelpText = "Some help text"

{ defaultConfig with MaxValueBindingWidth = 10 }

let title =
    "Great title of project"

type MyType() =
    member this.HelpText =
        "Some help text"

fsharp_max_function_binding_width

Control the maximum width for which function and member bindings should be in one line. Default = 40.

defaultConfig

let printScore score total = printfn "%i / %i" score total

type Triangle() =
    member this.CalculateSurface(width: int, height: int) = width * height / 2

{ defaultConfig with MaxFunctionBindingWidth = 10 }

let printScore score total =
    printfn "%i / %i" score total

type Triangle() =
    member this.CalculateSurface(width: int, height: int) =
        width * height / 2

fsharp_max_dot_get_expression_width

Control the maximum width for which (nested) SynExpr.DotGet expressions should be in one line. Default = 80.

defaultConfig

let job =
    JobBuilder
        .UsingJobData(jobDataMap)
        .UseSomethingElse()
        .Create<WrapperJob>()
        .Build()

{ defaultConfig with MaxDotGetExpressionWidth = 100 }

let job =
    JobBuilder.UsingJobData(jobDataMap).UseSomethingElse().Create<WrapperJob>().Build()

fsharp_multiline_block_brackets_on_same_column

Alternative way of formatting records, arrays and lists. This will align the braces at the same column level. Default = false.

defaultConfig

let myRecord =
    { Level = 1
      Progress = "foo"
      Bar = "bar"
      Street = "Bakerstreet"
      Number = 42 }

type Range =
    { From: float
      To: float }

let a =
    [| (1, 2, 3)
       (4, 5, 6)
       (7, 8, 9) |]

{ defaultConfig with MultilineBlockBracketsOnSameColumn = true }

let myRecord =
    {
        Level = 1
        Progress = "foo"
        Bar = "bar"
        Street = "Bakerstreet"
        Number = 42
    }

type Range =
    {
        From: float
        To: float
    }

let a =
    [|
        (1, 2, 3)
        (4, 5, 6)
        (7, 8, 9)
    |]

fsharp_newline_between_type_definition_and_members

Adds a new line between a type definition and its first member. Default = true.

defaultConfig

type Range =
    { From: float
      To: float }

    member this.Length = this.To - this.From

{ defaultConfig with NewlineBetweenTypeDefinitionAndMembers = false }

type Range =
    { From: float
      To: float }
    member this.Length = this.To - this.From

fsharp_align_function_signature_to_indentation

When a function signature exceeds the max_line_length, Fantomas will put all parameters on separate lines. This setting also places the equals sign and return type on a new line. Default = false.

defaultConfig

[<FunctionName("FormatCode")>]
let run 
    ([<HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "{*any}")>] req: HttpRequest)
    (log: ILogger)
    : HttpResponse =
    Http.main CodeFormatter.GetVersion format FormatConfig.FormatConfig.Default log req

{ defaultConfig with AlignFunctionSignatureToIndentation = true }

[<FunctionName("FormatCode")>]
let run
    ([<HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "{*any}")>] req: HttpRequest)
    (log: ILogger)
    : HttpResponse
    =
    Http.main CodeFormatter.GetVersion format FormatConfig.FormatConfig.Default log req

fsharp_alternative_long_member_definitions

Provides an alternative way of formatting long member and constructor definitions, where the difference is mainly in the equal sign and returned type placement. Default = false.

defaultConfig

type C
    (
        aVeryLongType: AVeryLongTypeThatYouNeedToUse,
        aSecondVeryLongType: AVeryLongTypeThatYouNeedToUse,
        aThirdVeryLongType: AVeryLongTypeThatYouNeedToUse
    ) =
    class
    end

type D() =
    member _.LongMethodWithLotsOfParameters
        (
            aVeryLongParam: AVeryLongTypeThatYouNeedToUse,
            aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse,
            aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse
        ) : ReturnType =
        42

{ defaultConfig with AlternativeLongMemberDefinitions = true }

type C
    (
        aVeryLongType: AVeryLongTypeThatYouNeedToUse,
        aSecondVeryLongType: AVeryLongTypeThatYouNeedToUse,
        aThirdVeryLongType: AVeryLongTypeThatYouNeedToUse
    )
    =
    class
    end

type D() =
    member _.LongMethodWithLotsOfParameters
        (
            aVeryLongParam: AVeryLongTypeThatYouNeedToUse,
            aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse,
            aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse
        )
        : ReturnType
        =
        42

fsharp_multi_line_lambda_closing_newline

Places the closing parenthesis of a multiline lambda argument on the next line. Default = false.

defaultConfig

let printListWithOffset a list1 =
    List.iter
        (fun { ItemOne = a } ->
            // print
            printfn "%s" a)
        list1

let printListWithOffset a list1 =
    list1
    |> List.iter
        (fun elem ->
            // print stuff
            printfn "%d" (a + elem))

{ defaultConfig with MultiLineLambdaClosingNewline = true }

let printListWithOffset a list1 =
    List.iter
        (fun { ItemOne = a } ->
            // print
            printfn "%s" a
        )
        list1

let printListWithOffset a list1 =
    list1
    |> List.iter (fun elem ->
        // print stuff
        printfn "%d" (a + elem)
    )

fsharp_experimental_keep_indent_in_branch

Breaks the normal indentation flow for the last branch of a pattern match or if/then/else expression. Only when the pattern match or if/then/else is the return value of a function or member.

This feature is considered experimental and is subject to change

defaultConfig

let main argv =
    let args = parse argv

    let instructions = Library.foo args

    if args.DryRun = RunMode.Dry then
        printfn "Would execute actions, but --dry-run was supplied: %+A" instructions
        0
    else
        // proceed with main method
        let output = Library.execute instructions
        // do more stuff
        0

{ defaultConfig with KeepIndentInBranch = true }

let main argv =
    let args = parse argv

    let instructions = Library.foo args

    if args.DryRun = RunMode.Dry then
        printfn "Would execute actions, but --dry-run was supplied: %+A" instructions
        0
    else
    // proceed with main method
    let output = Library.execute instructions
    // do more stuff
    0

fsharp_blank_lines_around_nested_multiline_expressions

Surround nested multi-line expressions with blank lines. Existing blank lines are always preserved (via trivia), with exception when fsharp_keep_max_number_of_blank_lines is used.
Top level expressions will always follow the 2020 blank lines revision principle. Default = true.

defaultConfig

let topLevelFunction () =
    printfn "Something to print"

    try
            nothing ()
    with
    | ex ->
        splash ()
    ()

let secondTopLevelFunction () =
    // ...
    ()

{ defaultConfig with BlankLinesAroundNestedMultilineExpressions = false }

let topLevelFunction () =
    printfn "Something to print"
    try
            nothing ()
    with
    | ex ->
        splash ()
    ()

let secondTopLevelFunction () =
    // ...
    ()

fsharp_bar_before_discriminated_union_declaration

Always use a | before every case in the declaration of a discriminated union. If false, a | character is used only in multiple-case discriminated unions, and is omitted in short single-case DUs. Default = false.

type MyDU = Short of int

{ defaultConfig with BarBeforeDiscriminatedUnionDeclaration = true }

type MyDU = | Short of int

fsharp_experimental_stroustrup_style

Please contribute to #1408.

fsharp_keep_max_number_of_blank_lines

Set maximal number of consecutive blank lines to keep from original source. It doesn't change number of new blank lines generated by Fantomas. Default=100

defaultConfig

open Foo


let x = 42

{ defaultConfig with KeepMaxNumberOfBlankLines = 1 }

open Foo

let x = 42

fsharp_strict_mode

If being set, pretty printing is only done via ASTs. Compiler directives, inline comments and block comments will be ignored. There are numerous situations when the information in the AST alone cannot restore the original code. Please do not use this setting for formatting hand written code! Valid use-case of this settings is code generation in projects like FsAst and Myriad. Default = false.

hand written code

// some great comment
let add a b =
#if INTERACTIVE
    42
#else
    a + b
#endif

{ defaultConfig with StrictMode = true }

let add a b = a + b

Ignore Files: .fantomasignore

starting version 4.1

To exclude files from formatting, create a .fantomasignore file in the root of your project. .fantomasignore uses gitignore syntax (processed via Ignore). Ignored files will be picked up when the Fantomas cli tool. Exclusion applies both to formatting and the format checking.

# Ignore Fable files
.fable/

# Ignore script files
*.fsx

Note that Fantomas only searches for a .fantomasignore file in or above its current working directory, if one exists; unlike Git, it does not traverse the filesystem for each input file to find an appropriate ignore file. (This is not true of the Fantomas daemon. The daemon can't rely on being invoked from the right place, and indeed there may not even be a well-defined notion of "right place" for the formatting tasks the daemon is required to perform, so it does search the filesystem for every file individually.)

Using the API

See CodeFormatter.fsi to view the public API of Fantomas.

Format selection

Fantomas provides limited support for formatting a selection of code in a file, using the CodeFormatter.FormatSelectionAsync function.
This is a very challenging domain, so not every scenario is covered. Fantomas is able to format:

The selection can have leading or trailing spaces but an exact selection is recommended. Invalid selections where no matching AST node is found will result in an exception. The return value of the function is the formatted code and the range of the formatted AST node. If the input selection range contained whitespaces, the result selection will contain the exact range of the formatted AST node.

Please consider contributing to this feature if you encounter any issues.

A git pre-commit hook sample

A very elegant and transparent way to use Fantomas is including it in a pre-commit git hook, by creating a .git/hooks/pre-commit file with:

#!/bin/sh
git diff --cached --name-only --diff-filter=ACM -z | xargs -0 $HOME/.dotnet/tools/fantomas
git diff --cached --name-only --diff-filter=ACM -z | xargs -0 git add

This script assumes you have installed Fantomas globally as a dotnet tool

Please use with caution as Fantomas is not without bugs.

FAKE Helpers

Fantomas also exposes some less official helper functions in Fantomas.Extras, these will be deprecated in the next major version. It is advised to run the fantomas instead when using FAKE, see example.

Updating to a new Fantomas version

By default, Fantomas adheres to the Microsoft F# code formatting guidelines. If these change, Fantomas will follow accordingly. Due to this reason, the output cannot be guaranteed to remain the same when upgrading to a new minor version.

If you are using Git for your source control, it is recommended to ignore commits where fantomas was updated using a .git-blame-ignore-revs file. Check out this blogpost for more details.