FSharp source code formatter
F# Other
Latest commit 0a7daec Mar 4, 2017 @dungpa committed on GitHub Publish NuGet packages (#207)

README.md

Fantomas

Fantomas logo

F# source code formatter, inspired by scalariform for Scala, ocp-indent for OCaml and PythonTidy for Python.

Build Status

Purpose

This project aims at formatting F# source files based on a given configuration. Fantomas will ensure correct indentation and consistent spacing between elements in the source files. We assume that the source files are parsable by F# compiler before feeding into the tool. Fantomas follows the formatting guideline being described in A comprehensive guide to F# Formatting Conventions.

Use cases

The project is developed with the following use cases in mind:

  • Reformatting an unfamiliar code base. It gives readability when you are not the one originally writing the code. To illustrate, the following example

    type Type
        = TyLam of Type * Type
        | TyVar of string
        | TyCon of string * Type list
        with override this.ToString () =
                match this with
                | TyLam (t1, t2) -> sprintf "(%s -> %s)" (t1.ToString()) (t2.ToString())
                | TyVar a -> a
                | TyCon (s, ts) -> s

will be rewritten to

```fsharp
type Type = 
    | TyLam of Type * Type
    | TyVar of string
    | TyCon of string * Type list
    override this.ToString() = 
        match this with
        | TyLam(t1, t2) -> sprintf "(%s -> %s)" (t1.ToString()) (t2.ToString())
        | TyVar a -> a
        | TyCon(s, ts) -> s
 ```
  • Converting from verbose syntax to light syntax. Feeding a source file in verbose mode, Fantomas will format it appropriately in light mode. This might be helpful for code generation since generating verbose source files is much easier. For example, this code fragment

    let Multiple9x9 () = 
        for i in 1 .. 9 do
            printf "\n";
            for j in 1 .. 9 do
                let k = i * j in
                printf "%d x %d = %2d " i j k;
            done;
        done;;
    Multiple9x9 ();;

is reformulated to ```fsharp let Multiple9x9() = for i in 1..9 do printf "\n" for j in 1..9 do let k = i * j printf "%d x %d = %2d " i j k

Multiple9x9()
```
  • Formatting F# signatures, especially those generated by F# compiler and F# Interactive.

For more complex examples, please take a look at F# outputs of 20 language shootout programs and 10 CodeReview.SE source files.

How to use

VS 2015

Fantomas is a part of Visual F# Power Tools extension compatible with Visual Studio 2015. The extension could be installed from Visual Studio Gallery. The usage instructions are the same as for VS 2012 extension.

VS 2012 and 2013 extension

Ivan Towlson kindly contributes the initial version of Fantomas VS extension. The user guide can be found here. This is available in the Visual Studio Gallery - search for "fantomas" in "Tools --> Extensions and Updates --> Online".

Ctrl + K D   -- format document
Ctrl + K F   -- format selection / format cursor position

You can also use Fantomas extension in Ivan's fsharp-vs-commands project.

Command line tool / API

You can fork this repo and compile the project with F# 3.0/.NET framework 4.0. Alternatively, Fantomas is also available via a NuGet package which contains both the library and the command line interface. For detailed guidelines, please read Fantomas: How to use.

FAKE build system

Fantomas can be easily integrated with FAKE build system. Here is a sample build.fsx:

#r "packages/FAKE/tools/FakeLib.dll"
#r "packages/Fantomas/lib/FantomasLib.dll"

open Fake
open Fantomas.FakeHelpers
open Fantomas.FormatConfig

// Properties
let buildDir = "./build/"
let fantomasConfig =
    { FormatConfig.Default with
            PageWidth = 120
            ReorderOpenDeclaration = true }

Target "CheckCodeFormat" (fun _ ->
    !! "src/**/*.fs"
      |> checkCode fantomasConfig
)

Target "FormatCode" (fun _ ->
    !! "src/**/*.fs"
      |> formatCode fantomasConfig
      |> Log "Formatted files: "
)

RunTargetOrDefault "CheckCodeFormat"

Trying Fantomas online

FantomasWeb, implemented by Taha Hachana, is accessible at http://fantomasweb.apphb.com/.

Fantomas plugin in Tsunami IDE

Taha also wrote a blog post on integrating Fantomas into Tsunami IDE.

Installation

The code base is written in F# 3.0/.NET framework 4.0. The solution file can be opened in Visual Studio 2012, Visual Studio 2013 and MonoDevelop/Xamarin Studio. NuGet is used to manage external packages. The test project depends on FsUnit and NUnit. However, the library project and command line interface have no dependency on external packages.

Testing and validation

We have tried to be careful in testing the project. There are 209 unit tests and 30 validated test examples, but it seems some corner cases of the language haven't been covered. Feel free to suggests tests if they haven't been handled correctly.

Why the name "Fantomas"?

There are a few reasons to choose the name as such. First, it starts with an "F" just like many other F# projects. Second, Fantomas is my favourite character in the literature. Finally, Fantomas means "ghost" in French; coincidentally F# ASTs and formatting rules are so mysterious to be handled correctly.

How to contribute

Would like to contribute? Discuss on issues and send pull requests. You can get started by helping us handle "You Take It" issues.

To get an understanding of the code, either:

Architectural notes

Fantomas' features are basically two commands: format a document or format a selection in the document.

They both consist in the same two stages:

  • parse the code and generate the F# AST (Abstract Syntax Tree). This is provided by the by the FSharp.Compiler.Services library (see the parse function in CodeFormatterImpl.fs).
  • rewrite the code based on the AST (previous step) and formatting settings.

The following sections describe the modules/function you will most likely be interested in looking at to get started.

The test project: Fantomas.Tests

The organization is really simple. For each F# language feature/constructs, there is a [Feature]Test.fs file. Examples:

  • StringTests.fs
  • UnionTests.fs
  • ...

Most of the tests are really simple and have this simple algorithm: assert that format [F# CODE] is equal to [FORMATTED F# CODE].

Example (from UnionTests.fs):

[<Test>]
let ``discriminated unions declaration``() =
    formatSourceString false "type X = private | A of AParameters | B" config
    |> prepend newline
    |> should equal """
type X = 
    private
    | A of AParameters
    | B
"""

The CodePrinter.genParsedInput function: rewrites formatted code

CodePrinter.genParsedInput (see CodePrinter.fs): what it basically does is traversing the AST corresponding to the code to format, and rewriting it according to the provided formatting options.

The FormatConfig type: format settings

Settings such as :

  • indent values in spaces
  • reorder open declarations
  • ...

See CodePrinter.fs.

How to play with fantomas on F# Interactive

The CodeFormatter.fsx script file allows you to test the code formatting behavior. See the function formatSrc: string -> unit that formats the string in input and prints it.

Contribution examples

The time it took to contribute is sometimes mentioned, as a side note.

Fixing code generation

  • Record type formatting generated invalid F# code in some cases (#197) - (2h fix)

Credits

We would like to gratefully thank the following persons for their contributions.

License

The library and tool are available under Apache 2.0 license. For more information see the License file.