diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index def54d1164..c3c0852ae9 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "fsdocs-tool": { - "version": "20.0.0-alpha-010", + "version": "20.0.0-alpha-018", "commands": [ "fsdocs" ] diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b63e81fe..c3478cf652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Changelog -## Unreleased +## 6.3.0-alpha-007 - 2024-01-27 ### Changed * Update FCS to 'Parser: parse primary ctor params as normal patterns', commit e2496896c128ccfde33c92f45bbe0d2aa738873a [#3034](https://github.com/fsprojects/fantomas/pull/3034) +* Multiline secondary constructor. [#3037](https://github.com/fsprojects/fantomas/issues/3037) [style guide](https://github.com/dotnet/docs/pull/39096) ## 6.3.0-alpha-006 - 2024-01-09 diff --git a/build.fsx b/build.fsx index e6959b968a..93d7dd4060 100644 --- a/build.fsx +++ b/build.fsx @@ -5,7 +5,6 @@ #r "nuget: Humanizer.Core, 2.14.1" open System -open System.Text.Json open System.IO open Fun.Build open CliWrap @@ -28,34 +27,6 @@ let cleanFolders (input: string seq) = Directory.Delete(dir, true)) } -/// Workaround for https://github.com/dotnet/sdk/issues/35989 -let restoreTools (ctx: Internal.StageContext) = - async { - let json = File.ReadAllText ".config/dotnet-tools.json" - let jsonDocument = JsonDocument.Parse(json) - let root = jsonDocument.RootElement - let tools = root.GetProperty("tools") - - let! installs = - tools.EnumerateObject() - |> Seq.map (fun tool -> - let version = tool.Value.GetProperty("version").GetString() - ctx.RunCommand $"dotnet tool install %s{tool.Name} --version %s{version}") - |> Async.Sequential - - let failedInstalls = - installs - |> Array.tryPick (function - | Ok _ -> None - | Error error -> Some error) - - match failedInstalls with - | None -> return 0 - | Some error -> - printfn $"%s{error}" - return 1 - } - let benchmarkAssembly = "src" "Fantomas.Benchmarks" @@ -90,7 +61,7 @@ let analysisReportsDir = "analysisreports" pipeline "Build" { workingDir __SOURCE_DIRECTORY__ - stage "RestoreTools" { run restoreTools } + stage "RestoreTools" { run "dotnet tool restore" } stage "Clean" { run ( cleanFolders @@ -200,7 +171,7 @@ pipeline "PushClient" { pipeline "Docs" { workingDir __SOURCE_DIRECTORY__ stage "Prepare" { - run restoreTools + run "dotnet tool restore" run "dotnet build -c Release src/Fantomas/Fantomas.fsproj" } stage "Watch" { diff --git a/docs/content/configuration.css b/docs/content/configuration.css index 00f034d7c8..ce8748cc0f 100644 --- a/docs/content/configuration.css +++ b/docs/content/configuration.css @@ -3,6 +3,9 @@ h3 { padding-inline: var(--spacing-100); font-size: var(--font-300); max-width: calc(100% - var(--configuration-icon-size) - var(--configuration-icon-size) - var(--spacing-100) - var(--spacing-100)); + & a { + padding-top: var(--spacing-600); + } } p > fantomas-setting, p > copy-to-clipboard { diff --git a/docs/content/fsdocs-theme.css b/docs/content/fsdocs-theme.css index 16e619a9dd..db7ddc018b 100644 --- a/docs/content/fsdocs-theme.css +++ b/docs/content/fsdocs-theme.css @@ -105,7 +105,7 @@ h1,h2,h3,h4,h5,h6 { } } -#fsdocs-page-menu { +#fsdocs-page-menu ul li { & a { color: var(--fantomas-600); } diff --git a/docs/content/webcomponents.js b/docs/content/webcomponents.js index c4ff0b2fc2..efc5201d3d 100644 --- a/docs/content/webcomponents.js +++ b/docs/content/webcomponents.js @@ -31,13 +31,21 @@ class Navigation extends LitElement { a { margin-top: var(--spacing-200); - color: var(--link-color); + color: var(--fantomas-800); display: inline-block; + text-decoration: none; + background-color: var(--fantomas-200); + padding: var(--spacing-50) var(--spacing-100); } + + a:hover { + background-color: var(--fantomas-400); + color: var(--fantomas-50); + } a:only-child { text-align: center; - flex: 1; + margin-inline: auto; } `; @@ -93,7 +101,7 @@ class CopyToClipboard extends LitElement { margin: 0; transition: all 200ms; position: absolute; - z-index: 1; + z-index: 101; left: 50%; transform: translateX(-50%); bottom: 100%; @@ -146,113 +154,113 @@ class FantomasSetting extends LitElement { } static styles = css` - :host { - display: inline-block; - } + :host { + display: inline-block; + } - :host([green]) iconify-icon { - color: #92DC84; - } + :host([green]) iconify-icon { + color: #92DC84; + } - :host([green]) .tooltip { - background-color: #92DC84; - } + :host([green]) .tooltip { + background-color: #92DC84; + } - :host([green]) .tooltip::after { - border-color: #92DC84 transparent transparent transparent; - } + :host([green]) .tooltip::after { + border-color: #92DC84 transparent transparent transparent; + } - :host([orange]) iconify-icon { - color: #F5BF4F; - } + :host([orange]) iconify-icon { + color: #F5BF4F; + } - :host([orange]) .tooltip { - background-color: #F5BF4F; - } + :host([orange]) .tooltip { + background-color: #F5BF4F; + } - :host([orange]) .tooltip::after { - border-color: #F5BF4F transparent transparent transparent; - } + :host([orange]) .tooltip::after { + border-color: #F5BF4F transparent transparent transparent; + } - :host([red]) iconify-icon { - color: #EA7268; - } + :host([red]) iconify-icon { + color: #EA7268; + } - :host([red]) .tooltip { - background-color: #EA7268; - } + :host([red]) .tooltip { + background-color: #EA7268; + } - :host([red]) .tooltip::after { - border-color: #EA7268 transparent transparent transparent; - } + :host([red]) .tooltip::after { + border-color: #EA7268 transparent transparent transparent; + } - :host([gr]) iconify-icon { - color: #00A8E2; - } + :host([gr]) iconify-icon { + color: #00A8E2; + } - :host([gr]) .tooltip { - background-color: #00A8E2; - } + :host([gr]) .tooltip { + background-color: #00A8E2; + } - :host([gr]) .tooltip::after { - border-color: #00A8E2 transparent transparent transparent; - } + :host([gr]) .tooltip::after { + border-color: #00A8E2 transparent transparent transparent; + } - div { - height: var(--configuration-icon-size); - position: relative; - } + div { + height: var(--configuration-icon-size); + position: relative; + } - img { - box-sizing: border-box; - padding: 4px; - background-color: #00A8E2; - height: var(--configuration-icon-size); - width: var(--configuration-icon-size); - border-radius: 12px; - display: inline-block; - } + img { + box-sizing: border-box; + padding: 4px; + background-color: #00A8E2; + height: var(--configuration-icon-size); + width: var(--configuration-icon-size); + border-radius: 12px; + display: inline-block; + } - img, iconify-icon { - position: relative; - cursor: pointer; + img, iconify-icon { + position: relative; + cursor: pointer; - &:hover + .tooltip { - visibility: visible; - opacity: 1; + &:hover + .tooltip { + visibility: visible; + opacity: 1; + } } - } - - .tooltip { - visibility: hidden; - opacity: 0; - white-space: nowrap; - font-size: 14px; - line-height: 1.5; - background-color: rgba(0, 0, 0, .95); - color: #FFF; - text-align: center; - border-radius: var(--radius); - padding: var(--spacing-100); - margin: 0; - transition: all 200ms; - position: absolute; - z-index: 1; - left: 50%; - transform: translateX(-50%); - bottom: 100%; - &::after { - content: " "; - position: absolute; - top: 100%; - left: 50%; - transform: translateX(-50%); - border-width: var(--radius); - border-style: solid; - border-color: rgba(0, 0, 0, .95) transparent transparent transparent; + .tooltip { + visibility: hidden; + opacity: 0; + white-space: nowrap; + font-size: 14px; + line-height: 1.5; + background-color: rgba(0, 0, 0, .95); + color: #FFF; + text-align: center; + border-radius: var(--radius); + padding: var(--spacing-100); + margin: 0; + transition: all 200ms; + position: absolute; + z-index: 101; + left: 50%; + transform: translateX(-50%); + bottom: 100%; + + &::after { + content: " "; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + border-width: var(--radius); + border-style: solid; + border-color: rgba(0, 0, 0, .95) transparent transparent transparent; + } } - } `; constructor(props) { diff --git a/docs/docs/end-users/Configuration.fsx b/docs/docs/end-users/Configuration.fsx index 565d333b73..4264990ae4 100644 --- a/docs/docs/end-users/Configuration.fsx +++ b/docs/docs/end-users/Configuration.fsx @@ -864,6 +864,14 @@ type D() = aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse ) : ReturnType = 42 + +type E() = + new + ( + aVeryLongType: AVeryLongTypeThatYouNeedToUse, + aSecondVeryLongType: AVeryLongTypeThatYouNeedToUse, + aThirdVeryLongType: AVeryLongTypeThatYouNeedToUse + ) = E() """ { FormatConfig.Default with AlternativeLongMemberDefinitions = true } diff --git a/src/Fantomas.Core.Tests/AlignedMultilineBracketStyleTests.fs b/src/Fantomas.Core.Tests/AlignedMultilineBracketStyleTests.fs index 2e8dd2c59d..10f0230380 100644 --- a/src/Fantomas.Core.Tests/AlignedMultilineBracketStyleTests.fs +++ b/src/Fantomas.Core.Tests/AlignedMultilineBracketStyleTests.fs @@ -1050,10 +1050,7 @@ type RequestParser<'ctx, 'a> = } static member internal Create - ( - consumedFields, - parse : 'ctx -> Request -> Async> - ) + (consumedFields, parse : 'ctx -> Request -> Async>) : RequestParser<'ctx, 'a> = { diff --git a/src/Fantomas.Core.Tests/BaseConstructorTests.fs b/src/Fantomas.Core.Tests/BaseConstructorTests.fs deleted file mode 100644 index bc65d29013..0000000000 --- a/src/Fantomas.Core.Tests/BaseConstructorTests.fs +++ /dev/null @@ -1,66 +0,0 @@ -module Fantomas.Core.Tests.BaseConstructorTests - -open NUnit.Framework -open FsUnit -open Fantomas.Core.Tests.TestHelpers - -[] -let ``multiple base constructors in record, 2111`` () = - formatSourceString - """ -type UnhandledWebException = - inherit Exception - - new(status: WebExceptionStatus, innerException: Exception) = - { inherit Exception(SPrintF1 - "Backend not prepared for this WebException with Status[%i]" - (int status), - innerException) } - - new(info: SerializationInfo, context: StreamingContext) = - { inherit Exception(info, context) } -""" - { config with MaxLineLength = 100 } - |> prepend newline - |> should - equal - """ -type UnhandledWebException = - inherit Exception - - new(status: WebExceptionStatus, innerException: Exception) = - { inherit - Exception( - SPrintF1 "Backend not prepared for this WebException with Status[%i]" (int status), - innerException - ) } - - new(info: SerializationInfo, context: StreamingContext) = { inherit Exception(info, context) } -""" - -[] -let ``single multiline base constructor, 2335`` () = - formatSourceString - """ -type FieldNotFoundException<'T>(obj:'T, field:string, specLink:string) = - inherit SwaggerSchemaParseException( - sprintf "Object MUST contain field `%s` (See %s for more details).\nObject:%A" - field specLink obj) -""" - { config with - SpaceBeforeClassConstructor = true - MaxLineLength = 90 } - |> prepend newline - |> should - equal - """ -type FieldNotFoundException<'T> (obj: 'T, field: string, specLink: string) = - inherit - SwaggerSchemaParseException ( - sprintf - "Object MUST contain field `%s` (See %s for more details).\nObject:%A" - field - specLink - obj - ) -""" diff --git a/src/Fantomas.Core.Tests/ClassTests.fs b/src/Fantomas.Core.Tests/ClassTests.fs index 782d63095c..1231463d30 100644 --- a/src/Fantomas.Core.Tests/ClassTests.fs +++ b/src/Fantomas.Core.Tests/ClassTests.fs @@ -861,10 +861,7 @@ type MaybeBuilder () = type MaybeBuilder() = member inline __.Bind // meh - ( - value, - binder: 'T -> 'U option - ) : 'U option = + (value, binder: 'T -> 'U option) : 'U option = Option.bind binder value """ @@ -896,10 +893,7 @@ type MaybeBuilder() = #else member inline __.Bind #endif - ( - value, - binder: 'T -> 'U option - ) : 'U option = + (value, binder: 'T -> 'U option) : 'U option = Option.bind binder value """ diff --git a/src/Fantomas.Core.Tests/ConstructorTests.fs b/src/Fantomas.Core.Tests/ConstructorTests.fs new file mode 100644 index 0000000000..8480e7db3f --- /dev/null +++ b/src/Fantomas.Core.Tests/ConstructorTests.fs @@ -0,0 +1,167 @@ +module Fantomas.Core.Tests.ConstructorTests + +open NUnit.Framework +open FsUnit +open Fantomas.Core.Tests.TestHelpers + +[] +let ``multiple base constructors in record, 2111`` () = + formatSourceString + """ +type UnhandledWebException = + inherit Exception + + new(status: WebExceptionStatus, innerException: Exception) = + { inherit Exception(SPrintF1 + "Backend not prepared for this WebException with Status[%i]" + (int status), + innerException) } + + new(info: SerializationInfo, context: StreamingContext) = + { inherit Exception(info, context) } +""" + { config with MaxLineLength = 100 } + |> prepend newline + |> should + equal + """ +type UnhandledWebException = + inherit Exception + + new(status: WebExceptionStatus, innerException: Exception) = + { inherit + Exception( + SPrintF1 "Backend not prepared for this WebException with Status[%i]" (int status), + innerException + ) } + + new(info: SerializationInfo, context: StreamingContext) = { inherit Exception(info, context) } +""" + +[] +let ``single multiline base constructor, 2335`` () = + formatSourceString + """ +type FieldNotFoundException<'T>(obj:'T, field:string, specLink:string) = + inherit SwaggerSchemaParseException( + sprintf "Object MUST contain field `%s` (See %s for more details).\nObject:%A" + field specLink obj) +""" + { config with + SpaceBeforeClassConstructor = true + MaxLineLength = 90 } + |> prepend newline + |> should + equal + """ +type FieldNotFoundException<'T> (obj: 'T, field: string, specLink: string) = + inherit + SwaggerSchemaParseException ( + sprintf + "Object MUST contain field `%s` (See %s for more details).\nObject:%A" + field + specLink + obj + ) +""" + +[] +let ``multiline secondary constructor, 3037`` () = + formatSourceString + """ +type IntersectionOptions + private + ( + primary: bool, + ?root: Element, + ?rootMargin: string, + ?threshold: ResizeArray, + ?triggerOnce: bool + ) + = + + new(?root: Element, + ?rootMargin: string, + ?threshold: ResizeArray, + ?triggerOnce: bool) = + + IntersectionOptions(true) +""" + { config with MaxLineLength = 80 } + |> prepend newline + |> should + equal + """ +type IntersectionOptions + private + ( + primary: bool, + ?root: Element, + ?rootMargin: string, + ?threshold: ResizeArray, + ?triggerOnce: bool + ) = + + new + ( + ?root: Element, + ?rootMargin: string, + ?threshold: ResizeArray, + ?triggerOnce: bool + ) = + + IntersectionOptions(true) +""" + +[] +let ``secondary constructor with xml doc`` () = + formatSourceString + """ +type IntersectionOptions + ( + primary: bool + ) = + + /// Good stuff + new (secondary: int) = IntersectionOptions(secondary = 0) +""" + config + |> prepend newline + |> should + equal + """ +type IntersectionOptions(primary: bool) = + + /// Good stuff + new(secondary: int) = IntersectionOptions(secondary = 0) +""" + +[] +let ``setting AlternativeLongMemberDefinitions should be respected in long secondary constructor`` () = + formatSourceString + """ +type StateMachine( + // meh +) = + new( + // also meh but with an int + x:int) as secondCtor = StateMachine() +""" + { config with + AlternativeLongMemberDefinitions = true } + |> prepend newline + |> should + equal + """ +type StateMachine + ( + // meh + ) + = + new + ( + // also meh but with an int + x: int) as secondCtor + = + StateMachine() +""" diff --git a/src/Fantomas.Core.Tests/CrampedMultilineBracketStyleTests.fs b/src/Fantomas.Core.Tests/CrampedMultilineBracketStyleTests.fs index 4f85fa7269..38b8faf1bf 100644 --- a/src/Fantomas.Core.Tests/CrampedMultilineBracketStyleTests.fs +++ b/src/Fantomas.Core.Tests/CrampedMultilineBracketStyleTests.fs @@ -1490,10 +1490,7 @@ type RequestParser<'ctx, 'a> = prohibited: ProhibitedRequestGetter list } static member internal Create - ( - consumedFields, - parse: 'ctx -> Request -> Async> - ) : RequestParser<'ctx, 'a> = + (consumedFields, parse: 'ctx -> Request -> Async>) : RequestParser<'ctx, 'a> = { consumedFields = consumedFields parse = parse prohibited = [] } diff --git a/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj b/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj index fc1f54d822..dcaf08cfa3 100644 --- a/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj +++ b/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj @@ -121,7 +121,7 @@ - + diff --git a/src/Fantomas.Core.Tests/FunctionDefinitionTests.fs b/src/Fantomas.Core.Tests/FunctionDefinitionTests.fs index 193e84a83c..bdbbd24c38 100644 --- a/src/Fantomas.Core.Tests/FunctionDefinitionTests.fs +++ b/src/Fantomas.Core.Tests/FunctionDefinitionTests.fs @@ -1106,12 +1106,7 @@ let longFunctionWithLongTupleParameter equal """ let longFunctionWithLongTupleParameter - ( - aVeryLongParam, - aSecondVeryLongParam, - aThirdVeryLongParam, - aFourthVeryLongParam - ) = + (aVeryLongParam, aSecondVeryLongParam, aThirdVeryLongParam, aFourthVeryLongParam) = // ... the body of the method follows () """ diff --git a/src/Fantomas.Core.Tests/KeepIndentInBranchTests.fs b/src/Fantomas.Core.Tests/KeepIndentInBranchTests.fs index ce553aa4a7..9ef21239c1 100644 --- a/src/Fantomas.Core.Tests/KeepIndentInBranchTests.fs +++ b/src/Fantomas.Core.Tests/KeepIndentInBranchTests.fs @@ -1114,18 +1114,22 @@ and [] Bar<'context, 'a> = a.Apply { new ApplyEval<_, _, _> with member __.Eval<'bb> - (a : - Foo<'innerContextLongLongLong, 'bb -> 'b> * - Foo<'innerContextLongLongLong, 'bb>) + ( + a : + Foo<'innerContextLongLongLong, 'bb -> 'b> * + Foo<'innerContextLongLongLong, 'bb> + ) = let (af, av) = a b.Apply { new ApplyEval<_, _, _> with member __.Eval<'cb> - (b : - Foo<'innerContextLongLongLong, 'cb -> 'b> * - Foo<'innerContextLongLongLong, 'bc>) + ( + b : + Foo<'innerContextLongLongLong, 'cb -> 'b> * + Foo<'innerContextLongLongLong, 'bc> + ) = let (bf, bv) = b diff --git a/src/Fantomas.Core.Tests/LetBindingTests.fs b/src/Fantomas.Core.Tests/LetBindingTests.fs index 9a200e94ed..ad81c7987f 100644 --- a/src/Fantomas.Core.Tests/LetBindingTests.fs +++ b/src/Fantomas.Core.Tests/LetBindingTests.fs @@ -2119,3 +2119,168 @@ let x::y = [] """ let x :: y = [] """ + +[] +let ``binding with multiple long paren tuple parameters`` () = + formatSourceString + """ +let foo + (a: RatherLongTypeName, b: RatherLongTypeName, c: RatherLongTypeName, d: RatherLongTypeName, e: RatherLongTypeName, f: RatherLongTypeName, g: RatherLongTypeName, h: RatherLongTypeName, i: RatherLongTypeName, j: RatherLongTypeName, k: RatherLongTypeName, l: RatherLongTypeName, m: RatherLongTypeName, n: RatherLongTypeName, o: RatherLongTypeName, p: RatherLongTypeName, q: RatherLongTypeName, r: RatherLongTypeName, s: RatherLongTypeName) + (t: RatherLongTypeName, u: RatherLongTypeName, v: RatherLongTypeName, w: RatherLongTypeName, x: RatherLongTypeName, y: RatherLongTypeName, z: RatherLongTypeName) = + // + () + +let bar + (a: RatherLongTypeName, b: RatherLongTypeName, c: RatherLongTypeName, d: RatherLongTypeName, e: RatherLongTypeName, f: RatherLongTypeName, g: RatherLongTypeName, h: RatherLongTypeName, i: RatherLongTypeName, j: RatherLongTypeName, k: RatherLongTypeName, l: RatherLongTypeName, m: RatherLongTypeName, n: RatherLongTypeName, o: RatherLongTypeName, p: RatherLongTypeName, q: RatherLongTypeName, r: RatherLongTypeName, s: RatherLongTypeName) + = + // + () +""" + config + |> prepend newline + |> should + equal + """ +let foo + ( + a: RatherLongTypeName, + b: RatherLongTypeName, + c: RatherLongTypeName, + d: RatherLongTypeName, + e: RatherLongTypeName, + f: RatherLongTypeName, + g: RatherLongTypeName, + h: RatherLongTypeName, + i: RatherLongTypeName, + j: RatherLongTypeName, + k: RatherLongTypeName, + l: RatherLongTypeName, + m: RatherLongTypeName, + n: RatherLongTypeName, + o: RatherLongTypeName, + p: RatherLongTypeName, + q: RatherLongTypeName, + r: RatherLongTypeName, + s: RatherLongTypeName + ) + ( + t: RatherLongTypeName, + u: RatherLongTypeName, + v: RatherLongTypeName, + w: RatherLongTypeName, + x: RatherLongTypeName, + y: RatherLongTypeName, + z: RatherLongTypeName + ) = + // + () + +let bar + ( + a: RatherLongTypeName, + b: RatherLongTypeName, + c: RatherLongTypeName, + d: RatherLongTypeName, + e: RatherLongTypeName, + f: RatherLongTypeName, + g: RatherLongTypeName, + h: RatherLongTypeName, + i: RatherLongTypeName, + j: RatherLongTypeName, + k: RatherLongTypeName, + l: RatherLongTypeName, + m: RatherLongTypeName, + n: RatherLongTypeName, + o: RatherLongTypeName, + p: RatherLongTypeName, + q: RatherLongTypeName, + r: RatherLongTypeName, + s: RatherLongTypeName + ) = + // + () +""" + +[] +let ``binding with multiple long paren tuple parameters, AlignFunctionSignatureToIndentation`` () = + formatSourceString + """ +let foo + (a: RatherLongTypeName, b: RatherLongTypeName, c: RatherLongTypeName, d: RatherLongTypeName, e: RatherLongTypeName, f: RatherLongTypeName, g: RatherLongTypeName, h: RatherLongTypeName, i: RatherLongTypeName, j: RatherLongTypeName, k: RatherLongTypeName, l: RatherLongTypeName, m: RatherLongTypeName, n: RatherLongTypeName, o: RatherLongTypeName, p: RatherLongTypeName, q: RatherLongTypeName, r: RatherLongTypeName, s: RatherLongTypeName) + (t: RatherLongTypeName, u: RatherLongTypeName, v: RatherLongTypeName, w: RatherLongTypeName, x: RatherLongTypeName, y: RatherLongTypeName, z: RatherLongTypeName) = + // + () + +let bar + (a: RatherLongTypeName, b: RatherLongTypeName, c: RatherLongTypeName, d: RatherLongTypeName, e: RatherLongTypeName, f: RatherLongTypeName, g: RatherLongTypeName, h: RatherLongTypeName, i: RatherLongTypeName, j: RatherLongTypeName, k: RatherLongTypeName, l: RatherLongTypeName, m: RatherLongTypeName, n: RatherLongTypeName, o: RatherLongTypeName, p: RatherLongTypeName, q: RatherLongTypeName, r: RatherLongTypeName, s: RatherLongTypeName) + = + // + () +""" + { config with + AlignFunctionSignatureToIndentation = true } + |> prepend newline + |> should + equal + """ +let foo + ( + a: RatherLongTypeName, + b: RatherLongTypeName, + c: RatherLongTypeName, + d: RatherLongTypeName, + e: RatherLongTypeName, + f: RatherLongTypeName, + g: RatherLongTypeName, + h: RatherLongTypeName, + i: RatherLongTypeName, + j: RatherLongTypeName, + k: RatherLongTypeName, + l: RatherLongTypeName, + m: RatherLongTypeName, + n: RatherLongTypeName, + o: RatherLongTypeName, + p: RatherLongTypeName, + q: RatherLongTypeName, + r: RatherLongTypeName, + s: RatherLongTypeName + ) + ( + t: RatherLongTypeName, + u: RatherLongTypeName, + v: RatherLongTypeName, + w: RatherLongTypeName, + x: RatherLongTypeName, + y: RatherLongTypeName, + z: RatherLongTypeName + ) + = + // + () + +let bar + ( + a: RatherLongTypeName, + b: RatherLongTypeName, + c: RatherLongTypeName, + d: RatherLongTypeName, + e: RatherLongTypeName, + f: RatherLongTypeName, + g: RatherLongTypeName, + h: RatherLongTypeName, + i: RatherLongTypeName, + j: RatherLongTypeName, + k: RatherLongTypeName, + l: RatherLongTypeName, + m: RatherLongTypeName, + n: RatherLongTypeName, + o: RatherLongTypeName, + p: RatherLongTypeName, + q: RatherLongTypeName, + r: RatherLongTypeName, + s: RatherLongTypeName + ) + = + // + () +""" diff --git a/src/Fantomas.Core.Tests/TypeDeclarationTests.fs b/src/Fantomas.Core.Tests/TypeDeclarationTests.fs index 677ea59fcb..937dfcbf5e 100644 --- a/src/Fantomas.Core.Tests/TypeDeclarationTests.fs +++ b/src/Fantomas.Core.Tests/TypeDeclarationTests.fs @@ -2794,18 +2794,22 @@ and [] Bar<'context, 'a> = a.Apply { new ApplyEval<_, _, _> with member __.Eval<'bb> - (a : - Foo<'innerContextLongLongLong, 'bb -> 'b> * - Foo<'innerContextLongLongLong, 'bb>) + ( + a : + Foo<'innerContextLongLongLong, 'bb -> 'b> * + Foo<'innerContextLongLongLong, 'bb> + ) = let (af, av) = a b.Apply { new ApplyEval<_, _, _> with member __.Eval<'cb> - (b : - Foo<'innerContextLongLongLong, 'cb -> 'b> * - Foo<'innerContextLongLongLong, 'bc>) + ( + b : + Foo<'innerContextLongLongLong, 'cb -> 'b> * + Foo<'innerContextLongLongLong, 'bc> + ) = let (bf, bv) = b diff --git a/src/Fantomas.Core/CodePrinter.fs b/src/Fantomas.Core/CodePrinter.fs index 3deeb25dde..184569b140 100644 --- a/src/Fantomas.Core/CodePrinter.fs +++ b/src/Fantomas.Core/CodePrinter.fs @@ -2850,19 +2850,15 @@ let genBinding (b: BindingNode) (ctx: Context) : Context = +> genSingleTextNode b.Equals let long (ctx: Context) = - let genParameters, hasSingleTupledArg = - match b.Parameters with - | [ Pattern.Paren parenNode ] -> + let endsWithTupleParameter = + match List.tryLast b.Parameters with + | Some(Pattern.Paren parenNode) -> match parenNode.Pattern with - | Pattern.Tuple tupleNode -> - (genSingleTextNode parenNode.OpeningParen - +> indentSepNlnUnindent (genTuplePatLong tupleNode |> genNode tupleNode) - +> sepNln - +> genSingleTextNode parenNode.ClosingParen - |> genNode parenNode), - true - | _ -> col sepNln b.Parameters genPat, false - | _ -> col sepNln b.Parameters genPat, false + | Pattern.Tuple _ -> true + | _ -> false + | _ -> false + + let genParameters = col sepNln b.Parameters genLongParenPatParameter let hasTriviaAfterLeadingKeyword = let beforeInline = @@ -2886,9 +2882,9 @@ let genBinding (b: BindingNode) (ctx: Context) : Context = +> indent +> sepNln +> genParameters - +> onlyIf (not hasSingleTupledArg || alternativeSyntax) sepNln + +> onlyIf (not endsWithTupleParameter || alternativeSyntax) sepNln +> leadingExpressionIsMultiline - (genReturnType (not hasSingleTupledArg || alternativeSyntax)) + (genReturnType (not endsWithTupleParameter || alternativeSyntax)) (fun isMultiline -> if (alternativeSyntax && Option.isSome b.ReturnType) || isMultiline then sepNln +> genSingleTextNode b.Equals @@ -3260,6 +3256,35 @@ let sepNlnBetweenTypeAndMembers (node: ITypeDefn) (ctx: Context) : Context = else ctx +[] +let inline (|ParameterWithTupleTypePattern|_|) (pat: Pattern) = + match pat with + | Pattern.Parameter parameterNode -> + match parameterNode.Type with + | Some t -> + match t with + | Type.Tuple _ -> ValueSome() + | _ -> ValueNone + | None -> ValueNone + | _ -> ValueNone + +/// Format a long parentheses parameter pattern in a binding or constructor. +/// Alternate formatting will applied when a paren tuple does not fit on the remainder of the line. +let genLongParenPatParameter (pat: Pattern) = + match pat with + | Pattern.Paren patParen -> + match patParen.Pattern with + | ParameterWithTupleTypePattern + | Pattern.Tuple _ -> + genSingleTextNode patParen.OpeningParen + +> expressionFitsOnRestOfLine + (genPat patParen.Pattern) + (indentSepNlnUnindent (genPat patParen.Pattern) +> sepNln) + +> genSingleTextNode patParen.ClosingParen + |> genNode patParen + | _ -> genPat pat + | _ -> genPat pat + let genImplicitConstructor (node: ImplicitConstructorNode) = let short = genXml node.XmlDoc @@ -3270,26 +3295,15 @@ let genImplicitConstructor (node: ImplicitConstructorNode) = +> genPat node.Pattern let long = - let genPats = - match node.Pattern with - | Pattern.Paren patParen -> - genSingleTextNode patParen.OpeningParen - +> expressionFitsOnRestOfLine - (genPat patParen.Pattern) - (indentSepNlnUnindent (genPat patParen.Pattern) +> sepNln) - +> genSingleTextNode patParen.ClosingParen - |> genNode patParen - | _ -> genPat node.Pattern - indentSepNlnUnindent ( genXml node.XmlDoc +> genOnelinerAttributes node.Attributes +> onlyIf node.Attributes.IsSome sepNln +> expressionFitsOnRestOfLine - (genAccessOpt node.Accessibility +> genPats) + (genAccessOpt node.Accessibility +> genLongParenPatParameter node.Pattern) (genAccessOpt node.Accessibility +> optSingle (fun _ -> sepNln) node.Accessibility - +> genPats) + +> genLongParenPatParameter node.Pattern) +> (fun ctx -> onlyIf ctx.Config.AlternativeLongMemberDefinitions sepNln ctx) ) @@ -3709,24 +3723,50 @@ let genMemberDefn (md: MemberDefn) = | MemberDefn.ExternBinding node -> genExternBinding node | MemberDefn.DoExpr node -> genExpr (Expr.Single node) | MemberDefn.ExplicitCtor node -> + let short = + genAccessOpt node.Accessibility + +> genSingleTextNode node.New + +> sepSpaceBeforeClassConstructor + +> genPat node.Pattern + +> optSingle (fun alias -> sepSpace +> !- "as" +> sepSpace +> genSingleTextNode alias) node.Alias + +> sepSpace + +> genSingleTextNode node.Equals + +> sepSpace + +> genExpr node.Expr + + let long = + leadingExpressionIsMultiline + (genAccessOpt node.Accessibility + +> genSingleTextNode node.New + +> sepSpaceBeforeClassConstructor + +> autoIndentAndNlnIfExpressionExceedsPageWidth (genLongParenPatParameter node.Pattern) + +> optSingle (fun alias -> sepSpace +> !- "as" +> sepSpace +> genSingleTextNode alias) node.Alias) + (fun isMultiline ctx -> + let genExpr = + genExpr node.Expr + +> optSingle + (fun thenExpr -> + sepNln + +> !- "then" + +> sepSpaceOrIndentAndNlnIfExpressionExceedsPageWidth (genExpr thenExpr)) + node.ThenExpr + + let short = genSingleTextNode node.Equals +> sepSpace +> genExpr + + let long ctx = + if ctx.Config.AlternativeLongMemberDefinitions then + indentSepNlnUnindent (genSingleTextNode node.Equals +> sepNln +> genExpr) ctx + else + (sepSpace +> genSingleTextNode node.Equals +> indentSepNlnUnindent genExpr) ctx + + if isMultiline then + long ctx + else + expressionFitsOnRestOfLine short long ctx) + genXml node.XmlDoc +> genAttributes node.Attributes - +> genAccessOpt node.Accessibility - +> genSingleTextNode node.New - +> sepSpaceBeforeClassConstructor - +> genPat node.Pattern - +> optSingle (fun alias -> sepSpace +> !- "as" +> sepSpace +> genSingleTextNode alias) node.Alias - +> sepSpace - +> genSingleTextNode node.Equals - +> sepSpaceOrIndentAndNlnIfExpressionExceedsPageWidth ( - genExpr node.Expr - +> optSingle - (fun thenExpr -> - sepNln - +> !- "then" - +> sepSpaceOrIndentAndNlnIfExpressionExceedsPageWidth (genExpr thenExpr)) - node.ThenExpr - ) + +> ifElse node.ThenExpr.IsSome long (expressionFitsOnRestOfLine short long) |> genNode (MemberDefn.Node md) | MemberDefn.LetBinding node -> genBindings true node.Bindings |> genNode (MemberDefn.Node md) | MemberDefn.Interface node -> diff --git a/src/Fantomas.Core/SyntaxOak.fs b/src/Fantomas.Core/SyntaxOak.fs index 387535c2b6..1cbc77d52e 100644 --- a/src/Fantomas.Core/SyntaxOak.fs +++ b/src/Fantomas.Core/SyntaxOak.fs @@ -2389,6 +2389,8 @@ type MemberDefnInheritNode(inheritKeyword: SingleTextNode, baseType: Type, range member val Inherit = inheritKeyword member val BaseType = baseType +/// Secondary constructor +/// new (pat: type) = expr type MemberDefnExplicitCtorNode ( xmlDoc: XmlDocNode option,