diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e5546a04b..13f8c6bcc2 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. [#3377](https://github.com/fsprojects/fantomas/pull/3377) + ## [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