From ed8093e62c7a418f0026cd4e5ff7dc605bb106be Mon Sep 17 00:00:00 2001 From: Philip Craig <689193+philipcraig@users.noreply.github.com> Date: Sat, 11 Jul 2026 15:39:09 +0000 Subject: [PATCH 1/2] Add fsharp_keep_single_case_union_multiline setting Add a new boolean configuration option that, when enabled, keeps a single-case discriminated union declaration on its own line instead of collapsing it onto the same line as the type name when it is short enough to fit. The option defaults to false, preserving the existing behaviour. It is wired through the FormatConfig record (and is therefore picked up automatically by the .editorconfig reflection-based parser/serializer) and applied in the single-case union branch of genTypeDefn in CodePrinter. Signature files share the same code path. Includes documentation and regression/idempotency tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 6 + docs/docs/end-users/Configuration.fsx | 25 +++- .../Fantomas.Core.Tests.fsproj | 1 + .../KeepSingleCaseUnionMultilineTests.fs | 141 ++++++++++++++++++ src/Fantomas.Core/CodePrinter.fs | 11 +- src/Fantomas.Core/FormatConfig.fs | 6 + 6 files changed, 185 insertions(+), 5 deletions(-) create mode 100644 src/Fantomas.Core.Tests/KeepSingleCaseUnionMultilineTests.fs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e5546a04b..32d5bd9918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] + +### Added + +- New `fsharp_keep_single_case_union_multiline` setting to keep single-case discriminated union declarations on a separate line instead of collapsing them onto the type name. [#3373](https://github.com/fsprojects/fantomas/pull/3373) + ## [8.0.0-alpha-012] - 2026-04-16 ### Changed diff --git a/docs/docs/end-users/Configuration.fsx b/docs/docs/end-users/Configuration.fsx index 98e358cbfd..3e0a0b59f0 100644 --- a/docs/docs/end-users/Configuration.fsx +++ b/docs/docs/end-users/Configuration.fsx @@ -1020,7 +1020,7 @@ printfn (*** include-output ***) formatCode - """ + """ type MyDU = Short of int """ """ @@ -1029,6 +1029,29 @@ fsharp_bar_before_discriminated_union_declaration = true (*** include-output ***) +(** + +### fsharp_keep_single_case_union_multiline + + +Keep the declaration of a single-case discriminated union on a separate line, instead of collapsing it onto the same line as the type name when it is short enough to fit. +*) + +(*** hide ***) +printfn + $"# Default\n{toEditorConfigName (nameof FormatConfig.Default.KeepSingleCaseUnionMultiline)} = {FormatConfig.Default.KeepSingleCaseUnionMultiline.ToString().ToLower()}" +(*** include-output ***) + +formatCode + """ + type MyDU = Short of int + """ + """ +fsharp_keep_single_case_union_multiline = true + """ + +(*** include-output ***) + (** ## Other diff --git a/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj b/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj index f1903212d9..8232f1eba7 100644 --- a/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj +++ b/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj @@ -82,6 +82,7 @@ + diff --git a/src/Fantomas.Core.Tests/KeepSingleCaseUnionMultilineTests.fs b/src/Fantomas.Core.Tests/KeepSingleCaseUnionMultilineTests.fs new file mode 100644 index 0000000000..41da10cf75 --- /dev/null +++ b/src/Fantomas.Core.Tests/KeepSingleCaseUnionMultilineTests.fs @@ -0,0 +1,141 @@ +module Fantomas.Core.Tests.KeepSingleCaseUnionMultilineTests + +open NUnit.Framework +open FsUnit +open Fantomas.Core.Tests.TestHelpers + +let config = + { config with + KeepSingleCaseUnionMultiline = true } + +[] +let ``single case union with field is kept multiline`` () = + formatSourceString + """ +type MyDU = Short of int +""" + config + |> prepend newline + |> should + equal + """ +type MyDU = + | Short of int +""" + +[] +let ``single case union without field is kept multiline`` () = + formatSourceString + """ +type A = | A +""" + config + |> prepend newline + |> should + equal + """ +type A = + | A +""" + +[] +let ``single case union with access modifier is kept multiline`` () = + formatSourceString + """ +type Foo = private Foo of int +""" + config + |> prepend newline + |> should + equal + """ +type Foo = + private | Foo of int +""" + +[] +let ``result is idempotent`` () = + let source = + """ +type MyDU = + | Short of int +""" + + formatSourceString source config + |> fun formatted -> formatSourceString formatted config + |> prepend newline + |> should + equal + """ +type MyDU = + | Short of int +""" + +[] +let ``combines with bar before discriminated union declaration`` () = + formatSourceString + """ +type MyDU = Short of int +""" + { config with + BarBeforeDiscriminatedUnionDeclaration = true } + |> prepend newline + |> should + equal + """ +type MyDU = + | Short of int +""" + +[] +let ``multi case union is unaffected`` () = + formatSourceString + """ +type MyDU = + | Short of int + | Long of string +""" + config + |> prepend newline + |> should + equal + """ +type MyDU = + | Short of int + | Long of string +""" + +[] +let ``single case union with member stays multiline`` () = + formatSourceString + """ +type MyDU = + | Short of int + member this.Value = 1 +""" + config + |> prepend newline + |> should + equal + """ +type MyDU = + | Short of int + + member this.Value = 1 +""" + +[] +let ``single case union in signature file is kept multiline`` () = + formatSignatureString + """namespace meh + +type Foo = Bar of int +""" + config + |> should + equal + """namespace meh + +type Foo = + | Bar of int +""" diff --git a/src/Fantomas.Core/CodePrinter.fs b/src/Fantomas.Core/CodePrinter.fs index d793de426b..71212a547c 100644 --- a/src/Fantomas.Core/CodePrinter.fs +++ b/src/Fantomas.Core/CodePrinter.fs @@ -3549,10 +3549,13 @@ let genTypeDefn (td: TypeDefn) = genSingleTextNode vis +> onlyIfNot singleCase.XmlDoc.IsNone sepNln) +> genUnionCase hasVerticalBar singleCase - expressionFitsOnRestOfLine - (sepSpace +> genCase hasVerticalBar) - (indentSepNlnUnindent (genCase true)) - ctx + if ctx.Config.KeepSingleCaseUnionMultiline then + indentSepNlnUnindent (genCase true) ctx + else + expressionFitsOnRestOfLine + (sepSpace +> genCase hasVerticalBar) + (indentSepNlnUnindent (genCase true)) + ctx | xs -> indentSepNlnUnindent (opt sepNln node.Accessibility genSingleTextNode diff --git a/src/Fantomas.Core/FormatConfig.fs b/src/Fantomas.Core/FormatConfig.fs index b96dda2856..54069789c6 100644 --- a/src/Fantomas.Core/FormatConfig.fs +++ b/src/Fantomas.Core/FormatConfig.fs @@ -221,6 +221,11 @@ type FormatConfig = [] BarBeforeDiscriminatedUnionDeclaration: bool + [] + [] + [] + KeepSingleCaseUnionMultiline: bool + [] [] [] @@ -273,6 +278,7 @@ type FormatConfig = ExperimentalKeepIndentInBranch = false BlankLinesAroundNestedMultilineExpressions = true BarBeforeDiscriminatedUnionDeclaration = false + KeepSingleCaseUnionMultiline = false MultilineBracketStyle = Aligned KeepMaxNumberOfBlankLines = 100 NewlineBeforeMultilineComputationExpression = true From 57f2f888dacd3b6d4ae759480a35f49667d6eaa1 Mon Sep 17 00:00:00 2001 From: Philip Craig <689193+philipcraig@users.noreply.github.com> Date: Sat, 11 Jul 2026 15:47:58 +0000 Subject: [PATCH 2/2] Update changelog with PR number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32d5bd9918..13f8c6bcc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -- New `fsharp_keep_single_case_union_multiline` setting to keep single-case discriminated union declarations on a separate line instead of collapsing them onto the type name. [#3373](https://github.com/fsprojects/fantomas/pull/3373) +- New `fsharp_keep_single_case_union_multiline` setting to keep single-case discriminated union declarations on a separate line instead of collapsing them onto the type name. [#3377](https://github.com/fsprojects/fantomas/pull/3377) ## [8.0.0-alpha-012] - 2026-04-16