Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [8.0.0] - 2026-09-15

### Changed

- Stable release.

## [8.0.0-beta-003] - 2026-09-12

### Changed
Expand Down Expand Up @@ -325,6 +331,10 @@

## [8.0.0-alpha-002] - 2025-12-15

### Added

- `CodeFormatter.FormatASTAsync(ast, config, source)`, beside the existing `FormatASTAsync(ast, source)`, so a syntax tree can be formatted with both a configuration and the source its trivia comes from. [#3207](https://github.com/fsprojects/fantomas/pull/3207)

### Changed

- Breaking: change default of MultilineBracketStyle from Cramped to Aligned. [#3200](https://github.com/fsprojects/fantomas/issues/3200)
Expand Down
22 changes: 6 additions & 16 deletions docs/docs/end-users/Chains.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,15 @@ Every code block on this page is Fantomas output, except where marked ⛔.
A ⛔ block is the alternative that was considered and turned down, shown so that the reasoning is visible rather than implied, and ✅ marks what Fantomas does instead.
The two markers appear wherever there was a real choice to make; elsewhere the output speaks for itself and goes unmarked.

## Status: a proposal, backed by an implementation
## Status: part of the F# style guide

The [F# style guide](https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting) currently says very little about how to lay out a long chain.
The rules described here are meant to fill that gap and to eventually become part of that guide.
Until they are officially adopted there, they remain a proposal being tested against real code, even though it is the layout Fantomas applies by default today.
The rules described here were proposed to the [F# style guide](https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting) and are now part of it.
Where the line breaks go is set out under [Formatting chained expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting#formatting-chained-expressions), and the space before a call's parenthesis, reported in 2021 and agreed at [fslang-design#648](https://github.com/fsharp/fslang-design/issues/648), under [Formatting application expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting#formatting-application-expressions).
This page is the longer account: what the rules are, the cases that shaped them, and why each one landed where it did.

As noted in the [Fantomas style guide page](./StyleGuide.html), the style itself is not decided in the Fantomas repository.
Those conversations happen at [fsharp/fslang-design](https://github.com/fsharp/fslang-design#style-guide), and they go much better when there is something concrete to react to.
A written proposal invites arguments about hypothetical snippets.
A proposal that is already implemented lets everyone run it over a real code base and see what it does to code they care about.

That is the order of work here: implement the rules in Fantomas first, use the implementation to find the awkward cases and settle them, then pitch the result upstream.
So treat this page as the current best answer rather than a settled one.
If you disagree with a rule, the discussion belongs at [fsharp/fslang-design](https://github.com/fsharp/fslang-design#style-guide), and having the implementation in hand is exactly what makes that discussion productive.

**One rule on this page has been through that loop already.** The space before a call's parenthesis was reported in 2021 and agreed at [fslang-design#648](https://github.com/fsharp/fslang-design/issues/648): a call keeps that space only when the whole thing being called is a plain dotted name.
Fantomas implements what was agreed, and it is set out under [The two space settings](#The-two-space-settings) below.
The style guide has yet to be amended to carry it, so that rule is agreed upstream without being written down there yet.
Everything else on this page, which is to say every rule about where the line breaks go, has not been through the loop at all and remains a proposal.
The rules were implemented in Fantomas first and run over real code to find the awkward cases before being pitched upstream, which is what made that discussion productive.
If you want a rule changed, the discussion belongs at [fsharp/fslang-design](https://github.com/fsharp/fslang-design#style-guide).

## What counts as a chain

Expand Down
121 changes: 117 additions & 4 deletions docs/docs/end-users/UpgradeGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,12 @@ fsharp_experimental_stroustrup_style = true
### .editorconfig
- `fsharp_max_dot_get_expression_width` was removed.

## v8 beta
## v8

### .editorconfig
- The default setting for `fsharp_multiline_bracket_style` is now `aligned`, to restore the previous behaviour use `fsharp_multiline_bracket_style = cramped`.
- A setting that carries the `fsharp_` prefix but is not one Fantomas has, such as the misspelling `fsharp_multiline_brackets_style`, is reported as a warning instead of being silently ignored, and where the intent is obvious the warning names the spelling that works. The same goes for a value a setting does not accept, a negative number among them. Formatting still runs, using the default for that setting. `fsharp_max_line_length` is reported too: `max_line_length` is the setting that applies.
- Keys and values are matched without regard to case, as the editorconfig specification says. `FSHARP_MAX_RECORD_WIDTH` and `fsharp_experimental_elmish = True` used to be ignored and now apply.

### console application
- Target framework is now `net10.0`.
Expand All @@ -109,6 +111,7 @@ fsharp_experimental_stroustrup_style = true
- A single file matched by `.fantomasignore` is reported on standard out, as `- A.fs was ignored by .fantomasignore.` Up to `v7` it printed nothing unless `--verbosity d` was given, even though a folder run whose only file was that same one reported it at normal verbosity. The two now agree.
- Everything a format or check run prints was rewritten. A run over a folder printed a bordered table of headings and counts; it now prints one sentence per file that changed and one line of counts. A script reading this output needs updating, and `--json` is there for a caller that has to act on the result rather than read it. The shapes are set out under "What a run prints now" below.
- `--profile` was removed. Use `fantomas profile <paths>`, which formats one file at a time so the timings can be compared and writes nothing. The flag wrote formatted files to disk as a side effect of measuring them, and the command does not, so it could not be kept working without silently changing what it did. Typing `--profile` reports that it is a command and prints the line to run instead.
- `--daemon` refuses the arguments that mean nothing to a daemon. `--check`, `--out`, `--force`, `--json` and input paths were accepted and silently ignored, so `fantomas --daemon ./src` looked like it would format a folder and did not. Any of them now exits 1 without starting the daemon. `--verbosity` is still accepted, and `--version` wins outright. `Fantomas.Client` launches the daemon with no other arguments, so no editor integration is affected.
- `--check` and `--daemon` can also be spelled `fantomas check` and `fantomas daemon`. Both flags keep working and are not deprecated, so nothing has to change. On a terminal the older spelling prints a one line note saying how it is spelled now; a redirected stream never sees it, so build logs and editor integrations are unaffected.
- A run with no input path formats the folder you are in, where it used to refuse with `No input path provided.` A script that relied on the refusal to catch a missing argument no longer gets one, and will format the working directory instead.
- A malformed command line exits 1 rather than 2. The documented exit codes have only ever been 0, 99 and 1; 2 came from the argument parser and was never one Fantomas chose.
Expand Down Expand Up @@ -273,8 +276,8 @@ If your build script creates the output folders before calling Fantomas, it can
### Formatting

Chains (dotted member access and calls) are laid out by a new set of rules, written up in full in
[Formatting chain expressions](./Chains.html). They are a proposal for the F# style guide and may still
change before `v8.0.0` is final.
[Formatting chain expressions](./Chains.html). They were proposed to the F# style guide and are now
part of it, under [Formatting chained expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting#formatting-chained-expressions).

The layout rules only apply once a chain has to break, so a chain that already fits on one line is
left alone. The spacing rule directly below is the exception: it applies whether or not the chain
Expand Down Expand Up @@ -320,7 +323,8 @@ it and `unbox<int> obj` is left as written.

This is the rule agreed at
[fslang-design#648](https://github.com/fsharp/fslang-design/issues/648), where the reasoning is
laid out in full.
laid out in full, and the style guide now carries it under
[Formatting application expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting#formatting-application-expressions).

#### A run of property access wraps instead of overflowing

Expand Down Expand Up @@ -424,6 +428,104 @@ let dotted ifaces =
)
```

#### `=`, `<`, `>`, `%` and `%%` no longer hang their right-hand side

These operators cannot start a line at the column of their left-hand side, where the parser reads
them as the `=` of a binding or as a quotation splice. `v7` kept them off that column by leaving the
operator on the first line and hanging the right-hand side under it, which needed a column count to
predict and moved when an unrelated part of the line changed length. `v8` follows the rule agreed
at [fsharp/fslang-design#836](https://github.com/fsharp/fslang-design/issues/836): the expression
fits on one line, or the operator stays with the left-hand side and the right-hand side moves one
level in, or the left-hand side itself spans several lines and the operator takes a line of its own.

```fsharp
// v7
let a =
someFunctionWithALongName argumentNumberOne argumentNumberTwo = anotherFunction
argumentNumberThree
argumentNumberFour

// v8
let a =
someFunctionWithALongName argumentNumberOne argumentNumberTwo =
anotherFunction argumentNumberThree argumentNumberFour
```

```fsharp
// v7
let b =
someFunctionWithALongName
argumentNumberOne
argumentNumberTwo
argumentNumberThree
argumentNumberFour
argumentNumberFive = anotherFunctionWithALongName argumentNumberOne argumentNumberTwo

// v8
let b =
someFunctionWithALongName
argumentNumberOne
argumentNumberTwo
argumentNumberThree
argumentNumberFour
argumentNumberFive
=
anotherFunctionWithALongName argumentNumberOne argumentNumberTwo
```

`fsharp_multiline_bracket_style = stroustrup` still overrides this for a right-hand side that opens
a bracket, which keeps hugging the operator.

#### `let!` and `use!` answer to `fsharp_max_value_binding_width`

`let` and `use` always did. `let!` and `use!` were printed by a branch of their own that consulted
the page width and nothing else, so a binding whose right-hand side was wider than the setting held
one line up to `max_line_length`. On default settings the two widths are 80 and 120, so a line of
110 characters carrying a body of 86 moves where it used to stay put:

```fsharp
// v7
let c =
async {
let! result = someFunctionWithALongName argumentNumberOne argumentNumberTwo argumentThree
return result
}

// v8
let c =
async {
let! result =
someFunctionWithALongName argumentNumberOne argumentNumberTwo argumentThree

return result
}
```

#### Stroustrup reaches object expressions

This applies with **`fsharp_multiline_bracket_style = stroustrup`**. `aligned` and `cramped` are
untouched.

An object expression was printed by the `aligned` branch whatever the setting said, which made
stroustrup the only bracket style that did not reach every bracket it names. It now lands where a
record already did, and the same applies to a binding whose signature broke across lines and to a
match clause under `fsharp_experimental_keep_indent_in_branch`, both of which used to fall back to
`aligned`:

```fsharp
// v7
let disposable =
{ new System.IDisposable with
member _.Dispose() = printfn "disposed"
}

// v8
let disposable = {
new System.IDisposable with
member _.Dispose() = printfn "disposed"
}
```

### Fantomas.Core API

These only affect you if you consume `Fantomas.Core` as a library. Formatting source text through
Expand All @@ -437,6 +539,17 @@ has to be rebuilt against `v8`.

#### Exceptions

- `ParseException` is a class deriving from `FormatException` rather than an F# `exception`, so `:? FormatException` now catches every way formatting can fail. Its diagnostics are reachable as a property rather than only through the exception pattern; raising and constructing it are unchanged:

```fsharp
// v7
| ParseException diagnostics -> ...

// v8
| :? ParseException as e -> e.Diagnostics
```

`Message` names the first error by position instead of dumping every diagnostic through `%A`.
- `InvariantViolationException` was added. It derives from `FormatException` and is raised when Fantomas reaches a state its own model says is impossible, which always means a bug in Fantomas rather than a problem with your code.
If you catch `FormatException`, you already catch this.
- `DefineParseException` was added, raised when one or more conditional compilation define combinations produce invalid syntax trees.
Expand Down