Introduction
Early this year we announced our plans for Fantomas 6.
We are happy to announce that we have wrapped up the development of version 6 and a preview is now available on NuGet.
What’s new?
Fantomas 6 is a major release and contains a lot of new features and improvements. In this post we will highlight some of the most important changes.
Parallel formatting
Fantomas 6 is now able to format multiple files in parallel. This is a huge improvement for large projects.
This happens out of the box and you don’t have to do anything to benefit from this.
A whole lot of Stroustrup
In recent months, the Stroustrup style has been officially enlisted in the FSharp style guide. This means we want to support this style in Fantomas as a first class citizen.
let band = {
Vocals = "John"
Bass = "Paul"
Guitar = "George"
Drums = "Ringo"
}
let songs = [
"Come Together"
"Hey Jude"
"Yesterday"
"Yellow Submarine"
"Here Comes the Sun"
]
type Record = {
ReleaseDate : DateTime
Title : string
Artist : string
}
Enable this style by setting
fsharp_multiline_bracket_style = stroustrup
in your .editorconfig
file.
Computation expressions
Computation expressions are an interesting topic, on the one hand they are respecting Stroustrup
style by themselves.
And on the other hand, they could also be formatted as Stroustrup
in combination with a let binding.
let something =
task {
let! thing = otherThing ()
return 5
}
// versus
let something = task {
let! thing = otherThing ()
return 5
}
As we are a bit split on this topic, we decided to make this configurable. By default, we will format computation expressions on the next line in a binding. And you can enable control this behavior by setting
fsharp_newline_before_multiline_computation_expression = true
// Default: fsharp_newline_before_multiline_computation_expression = true
let something =
task {
let! thing = otherThing ()
return 5
}
// versus
// fsharp_newline_before_multiline_computation_expression = false
let something = task {
let! thing = otherThing ()
return 5
}
Cursor API
When you are in the heat of writing code, you don’t want to be bothered by formatting things manually. Luckily, Fantomas can help you out with this. By formatting consistently, your raw code can jump around quite a bit. And one side effect of that, is that your cursor will jump around as well.
In v6 we have added a new API for editors to be able to know where the cursor should be after formatting. JetBrains Rider is the first editor to support this API, and I suspect other editors will follow accordingly. This should improve the overall experience of using Fantomas in your editor.
Code generation
Fantomas can be used to generate F# code. This feature has been around for quite some time, but it was a bit tricky to use. Tricky, as in, you had to know how your way around the untyped AST to generate the code. The AST from the F# parser has historically always been a bit challenging to work with in Fantomas. Our use-case is slightly different than the compiler’s and the tree we get is not always the most convenient to work with.
We addressed this trial by inducing our own syntax tree in “Project Dallas”. In v6, we made those types public and added an API to generate code from them. The result is that you can now generate code in a much more convenient way.
The old API:
#r "nuget: Fantomas.Core, 6.0-alpha-*"
open FSharp.Compiler.Text
open FSharp.Compiler.Xml
open FSharp.Compiler.Syntax
open FSharp.Compiler.SyntaxTrivia
open Fantomas.Core
let implementationSyntaxTree =
ParsedInput.ImplFile(
ParsedImplFileInput(
"filename.fsx",
true,
QualifiedNameOfFile(Ident("", Range.Zero)),
[],
[],
[ SynModuleOrNamespace(
[],
false,
SynModuleOrNamespaceKind.AnonModule,
[ SynModuleDecl.Let(
false,
[ SynBinding(
None,
SynBindingKind.Normal,
false,
false,
[],
PreXmlDoc.Empty,
SynValData(None, SynValInfo([], SynArgInfo([], false, None)), None),
SynPat.Named(SynIdent(Ident("a", Range.Zero), None), false, None, Range.Zero),
None,
SynExpr.Const(SynConst.Int32(0), Range.Zero),
Range.Zero,
DebugPointAtBinding.Yes Range.Zero,
{ EqualsRange = Some Range.Zero
InlineKeyword = None
LeadingKeyword = SynLeadingKeyword.Let Range.Zero }
) ],
Range.Zero
) ],
PreXmlDoc.Empty,
[],
None,
Range.Zero,
{ LeadingKeyword = SynModuleOrNamespaceLeadingKeyword.None }
) ],
(false, false),
{ ConditionalDirectives = []
CodeComments = [] },
Set.empty
)
)
CodeFormatter.FormatASTAsync(implementationSyntaxTree)
|> Async.RunSynchronously
|> fun result -> printfn "%s" result.Code
The Oak API:
#r "nuget: Fantomas.Core, 6.0-alpha-*"
open FSharp.Compiler.Text
open Fantomas.Core
open Fantomas.Core.SyntaxOak
let implementationOak =
Oak(
[],
[ ModuleOrNamespaceNode(
None,
[ BindingNode(
None,
None,
MultipleTextsNode([ SingleTextNode("let", Range.Zero) ], Range.Zero),
false,
None,
None,
Choice1Of2(IdentListNode([IdentifierOrDot.Ident(SingleTextNode("a", Range.Zero))], Range.Zero)),
None,
[],
None,
SingleTextNode("=", Range.Zero),
Expr.Constant(Constant.FromText(SingleTextNode("0", Range.Zero))),
Range.Zero
)
|> ModuleDecl.TopLevelBinding ],
Range.Zero
) ],
Range.Zero
)
CodeFormatter.FormatOakAsync(implementationOak)
|> Async.RunSynchronously
|> printfn "%s"
Note that we do still support the old API, but we recommend using the new API instead.
Fabulous.AST
Even thought the Oak API simplifies generating code, it can still be a bit verbose. Depending on your use-case, you might want to generate code in a more declarative way.
For this, a new library called Fabulous.AST has been created. It is a library that will provide a nice DSL similar to other Fabulous projects.
More on that in the near future.
Verbose output
Fantomas always made a lot of noise when you ran it. We reported a line about start and finish of each file. This can be useful, but it can also be a bit annoying. In v6, by default, we silenced this output.
v5:
~#@❯ dotnet fantomas src -r
Processing src\fsc\fscmain.fs
'src\fsc\fscmain.fs' was unchanged
Processing src\FSharp.Build\CreateFSharpManifestResourceName.fs
'src\FSharp.Build\CreateFSharpManifestResourceName.fs' was unchanged
Processing src\FSharp.Build\Fsc.fs
'src\FSharp.Build\Fsc.fs' was unchanged
Processing src\FSharp.Build\FSharpCommandLineBuilder.fs
'src\FSharp.Build\FSharpCommandLineBuilder.fs' was unchanged
Processing src\FSharp.Build\FSharpEmbedResourceText.fs
'src\FSharp.Build\FSharpEmbedResourceText.fs' was unchanged
Processing src\FSharp.Build\FSharpEmbedResXSource.fs
'src\FSharp.Build\FSharpEmbedResXSource.fs' was unchanged
Processing src\FSharp.Build\Fsi.fs
'src\FSharp.Build\Fsi.fs' was unchanged
Processing src\FSharp.Build\InternalsVisibleTo.fs
'src\FSharp.Build\InternalsVisibleTo.fs' was unchanged
Processing src\FSharp.Build\MapSourceRoots.fs
...
v6:
~#@❯ dotnet fantomas src
Formatted │ 119 │ Ignored │ 99 │ Unchanged │ 183 │ Errored │ 0
If you want to see the verbose output, you can use the --verbose
(or -v
) flag.
Miscellaneous
Public API
The CodeFormatter
module represents the public API of Fantomas.Core
. We tweaked the API a bit to make it more consistent.
Deprecations
- Settings
fsharp_multiline_block_brackets_on_same_column
andfsharp_experimental_stroustrup_style
have been deprecated. You can usefsharp_multiline_bracket_style
instead. Acceptingcramped
(default),aligned
andstroustrup
. --recursive
is no longer available on the command line. We will process all files in the given directory recursively by default. If you want to excluded any files or directories, you can use a .fantomasignore file.
Last call for feedback
Currently, v6 is still marked as alpha. The reason for this is that we will want to accept any feedback before we release v6 as stable. Once we label it as beta, we will not accept any breaking changes anymore. That is why we are giving the community some more time to test v6 and give us feedback.
Acknowledgements
Fantomas v6 is a remarkable achievement as many people have contributed to it. I believe this might be the first big release where I didn’t contribute the majority of the code. Which is a very healthy sign for the project.
I would like to thank the following people in particular for their contributions:
- Josh DeGraw for all his effort around the Stroustrup style. Both in the style guide and Fantomas!
- David Schaefer for his work on the command line output, many pull request review and various tweaks!
- Jimmy Byrd for taking a crack at parallel formatting in the command line tool!
- Edgar Gonzalez for exposing the
Oak
model and dogfooding it. - Alex Berezhnykh for his collaboration on the Cursor API.
The Fantomas crew having some fun during a Discord call
Happy formatting,
Florian
Photo by Parmanand Jagnandan on Unsplash