diff --git a/docs/Documentation.md b/docs/Documentation.md index 4102ad1dd1..0b5c97ab9c 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -472,8 +472,9 @@ let WebApp = ### fsharp_max_record_width -Control the maximum width for which records should be in one line. -Default = 40. +Control the maximum width for which records should be in one line. Default = 40. +Requires `fsharp_record_multiline_formatter` to be `character_width` to take +effect. `defaultConfig` @@ -496,6 +497,99 @@ let myInstance = Length = 90 } ``` +### fsharp_max_record_number_of_items + +Control the maximum number of fields for which records should be in one line. +Default = 1. Requires `fsharp_array_or_list_multiline_formatter` to be +`number_of_items` to take effect. + +`defaultConfig` + +```fsharp +type R = { x: int } + +type S = { x: int; y: string } + +type T = { x: int; y: string; z: float } + +let myRecord = { r = 3 } + +let myRecord' = { r with x = 3 } + +let myRecord'' = { r with x = 3; y = "hello" } + +let myRecord''' = { r with x = 3; y = "hello"; z = 0.0 } +``` + +`{ defaultConfig with MaxRecordSize = 2; RecordMultilineFormatter = +MultilineFormatterType.NumberOfItems }` + +```fsharp +type R = { x: int } + +type S = { x: int; y: string } + +type T = + { x: int + y: string + z: float } + +let myRecord = { r = 3 } + +let myRecord' = { r with x = 3 } + +let myRecord'' = { r with x = 3; y = "hello" } + +let myRecord''' = + { r with + x = 3 + y = "hello" + z = 0.0 } +``` + +### fsharp_record_multiline_formatter + +Split records expressions/statements into multiple lines based on the given +condition. `character_width` uses character count of the expression, controlled +by `fsharp_max_record_width`. `number_of_items` uses the number of fields in the +record, controlled by `fsharp_max_record_number_of_items`. Default = +`character_width`. Note that in either case, record expressions/statements are +still governed by `max_line_length`. + +`defaultConfig` + +```fsharp +type R = { x: int } + +type S = { x: int; y: string } + +let myRecord = { r = 3 } + +let myRecord' = { r with x = 3 } + +let myRecord'' = { r with x = 3; y = "hello" } +``` + +`{ defaultConfig with RecordMultilineFormatter = +MultilineFormatterType.NumberOfItems }` + +```fsharp +type R = { x: int } + +type S = + { x: int + y: string } + +let myRecord = { x = 3 } + +let myRecord' = { r with x = 3 } + +let myRecord'' = + { r with + x = 3 + y = "hello" } +``` + ### fsharp_max_array_or_list_width Control the maximum width for which lists and arrays can be in one line. Default diff --git a/src/Fantomas.Tests/Fantomas.Tests.fsproj b/src/Fantomas.Tests/Fantomas.Tests.fsproj index a88e43dfd1..8946dce738 100644 --- a/src/Fantomas.Tests/Fantomas.Tests.fsproj +++ b/src/Fantomas.Tests/Fantomas.Tests.fsproj @@ -74,7 +74,8 @@ - + + diff --git a/src/Fantomas.Tests/FormatConfigEditorConfigurationFileTests.fs b/src/Fantomas.Tests/FormatConfigEditorConfigurationFileTests.fs index 77e11bd4ca..5fa2626c12 100644 --- a/src/Fantomas.Tests/FormatConfigEditorConfigurationFileTests.fs +++ b/src/Fantomas.Tests/FormatConfigEditorConfigurationFileTests.fs @@ -204,7 +204,7 @@ let ``print default editorconfig settings`` () = |> printfn "%s" [] -let ``number_of_items parsing tests`` () = +let ``list and array number_of_items parsing tests`` () = let editorConfig = """ [*.fs] fsharp_array_or_list_multiline_formatter = number_of_items @@ -225,7 +225,7 @@ fsharp_max_array_or_list_number_of_items = 4 == NumberOfItems [] -let ``character_width parsing test with single option`` () = +let ``list and array character_width parsing test with single option`` () = let editorConfig = """ [*.fs] fsharp_max_array_or_list_width = 123 @@ -240,3 +240,40 @@ fsharp_max_array_or_list_width = 123 EditorConfig.readConfiguration fsharpFile.FSharpFile config.MaxArrayOrListWidth == 123 + +[] +let ``record number_of_items parsing tests`` () = + let editorConfig = """ +[*.fs] +fsharp_record_multiline_formatter = number_of_items +fsharp_max_record_number_of_items = 4 +""" + + use configFixture = + new ConfigurationFile(defaultConfig, content = editorConfig) + + use fsharpFile = new FSharpFile() + + let config = + EditorConfig.readConfiguration fsharpFile.FSharpFile + + config.MaxRecordNumberOfItems == 4 + + config.RecordMultilineFormatter == NumberOfItems + +[] +let ``record character_width parsing test with single option`` () = + let editorConfig = """ +[*.fs] +fsharp_max_record_width = 123 +""" + + use configFixture = + new ConfigurationFile(defaultConfig, content = editorConfig) + + use fsharpFile = new FSharpFile() + + let config = + EditorConfig.readConfiguration fsharpFile.FSharpFile + + config.MaxRecordWidth == 123 diff --git a/src/Fantomas.Tests/NumberOfItemsTests.fs b/src/Fantomas.Tests/NumberOfItemsListOrArrayTests.fs similarity index 98% rename from src/Fantomas.Tests/NumberOfItemsTests.fs rename to src/Fantomas.Tests/NumberOfItemsListOrArrayTests.fs index 6efe9bf42d..1bde9dd2f9 100644 --- a/src/Fantomas.Tests/NumberOfItemsTests.fs +++ b/src/Fantomas.Tests/NumberOfItemsListOrArrayTests.fs @@ -1,4 +1,4 @@ -module Fantomas.Tests.NumberOfItemsTests +module Fantomas.Tests.NumberOfItemsListOrArrayTests open NUnit.Framework open FsUnit @@ -284,7 +284,7 @@ let ``long expressions with number of items set to 3 will get split due to max l """ [] -let ``character width with explicit width lists are formatted properly`` () = +let ``character width with explicit width sized lists are formatted properly`` () = formatSourceString false """ let x = [ a; b; c ] let y = [ longValueThatIsALotOfCharactersSoooooLong; longValueThatIsALotOfCharactersSoooooLong ] diff --git a/src/Fantomas.Tests/NumberOfItemsRecordTests.fs b/src/Fantomas.Tests/NumberOfItemsRecordTests.fs new file mode 100644 index 0000000000..cb0eb4945a --- /dev/null +++ b/src/Fantomas.Tests/NumberOfItemsRecordTests.fs @@ -0,0 +1,987 @@ +module Fantomas.Tests.NumberOfItemsRecordTests + +open NUnit.Framework +open FsUnit +open Fantomas.Tests.TestHelper +open Fantomas.FormatConfig + +let config = + { config with + RecordMultilineFormatter = NumberOfItems } + +[] +let ``single member record stays on one line`` () = + formatSourceString false """let a = { Foo = "bar" } +""" config + |> prepend newline + |> should equal """ +let a = { Foo = "bar" } +""" + +[] +let ``record instance`` () = + formatSourceString false """let myRecord = + { Level = 1 + Progress = "foo" + Bar = "bar" + Street = "Bakerstreet" + Number = 42 } +""" config + |> prepend newline + |> should equal """ +let myRecord = + { Level = 1 + Progress = "foo" + Bar = "bar" + Street = "Bakerstreet" + Number = 42 } +""" + +[] +let ``nested record`` () = + formatSourceString false """let myRecord = + { Level = 1 + Progress = "foo" + Bar = { Zeta = "bar" } + Address = + { Street = "Bakerstreet" + ZipCode = "9000" } + Number = 42 } +""" config + |> prepend newline + |> should equal """ +let myRecord = + { Level = 1 + Progress = "foo" + Bar = { Zeta = "bar" } + Address = + { Street = "Bakerstreet" + ZipCode = "9000" } + Number = 42 } +""" + +[] +let ``update record`` () = + formatSourceString false """let myRecord = + { myOldRecord + with Level = 2 + Bar = "barry" + Progress = "fooey" } +""" config + |> prepend newline + |> should equal """ +let myRecord = + { myOldRecord with + Level = 2 + Bar = "barry" + Progress = "fooey" } +""" + +[] +let ``update record with single field`` () = + formatSourceString false """let myRecord = + { myOldRecord + with Level = 2 } +""" config + |> prepend newline + |> should equal """ +let myRecord = { myOldRecord with Level = 2 } +""" + +[] +let ``record instance with inherit keyword`` () = + formatSourceString false """let a = + { inherit ProjectPropertiesBase<_>(projectTypeGuids, factoryGuid, targetFrameworkIds, dotNetCoreSDK) + buildSettings = FSharpBuildSettings() + targetPlatformData = targetPlatformData } +""" config + |> prepend newline + |> should equal """ +let a = + { inherit ProjectPropertiesBase<_>(projectTypeGuids, factoryGuid, targetFrameworkIds, dotNetCoreSDK) + buildSettings = FSharpBuildSettings() + targetPlatformData = targetPlatformData } +""" + +[] +let ``record instance with inherit keyword and no fields`` () = + formatSourceString false """let a = + { inherit ProjectPropertiesBase<_>(projectTypeGuids, factoryGuid, targetFrameworkIds, dotNetCoreSDK) } +""" config + |> prepend newline + |> should equal """ +let a = + { inherit ProjectPropertiesBase<_>(projectTypeGuids, factoryGuid, targetFrameworkIds, dotNetCoreSDK) } +""" + +[] +let ``type with record instance with inherit keyword`` () = + formatSourceString false """type ServerCannotBeResolvedException = + inherit CommunicationUnsuccessfulException + + new(message) = + { inherit CommunicationUnsuccessfulException(message) }""" config + |> prepend newline + |> should equal """ +type ServerCannotBeResolvedException = + inherit CommunicationUnsuccessfulException + + new(message) = { inherit CommunicationUnsuccessfulException(message) } +""" + +[] +let ``anonymous record`` () = + formatSourceString false """let meh = + {| Level = 1 + Progress = "foo" + Bar = "bar" + Street = "Bakerstreet" + Number = 42 |} +""" config + |> prepend newline + |> should equal """ +let meh = + {| Level = 1 + Progress = "foo" + Bar = "bar" + Street = "Bakerstreet" + Number = 42 |} +""" + +[] +let ``anonymous record with single field update`` () = + formatSourceString false """let a = {| foo with Level = 7 |} +""" config + |> prepend newline + |> should equal """ +let a = {| foo with Level = 7 |} +""" + +[] +let ``anonymous record with multiple field update`` () = + formatSourceString false """let a = {| foo with Level = 7; Square = 9 |} +""" ({ config with MaxRecordWidth = 35 }) + |> prepend newline + |> should equal """ +let a = + {| foo with + Level = 7 + Square = 9 |} +""" + +[] +let ``anonymous type`` () = + formatSourceString false """type a = {| foo : string; bar : string |} +""" config + |> prepend newline + |> should equal """ +type a = + {| foo: string + bar: string |} +""" + +[] +let ``anonymous record with single field`` () = + formatSourceString false """let a = {| A = "meh" |} +""" config + |> prepend newline + |> should equal """ +let a = {| A = "meh" |} +""" + +[] +let ``anonymous record with child records`` () = + formatSourceString false """ +let anonRecord = + {| A = {| A1 = "string";A2LongerIdentifier = "foo" |}; + B = {| B1 = 7 |} + C= { C1 = "foo"; C2LongerIdentifier = "bar"} + D = { D1 = "bar" } |} +""" config + |> prepend newline + |> should equal """ +let anonRecord = + {| A = + {| A1 = "string" + A2LongerIdentifier = "foo" |} + B = {| B1 = 7 |} + C = + { C1 = "foo" + C2LongerIdentifier = "bar" } + D = { D1 = "bar" } |} +""" + +[] +let ``record as parameter to function`` () = + formatSourceString false """let configurations = + buildConfiguration { XXXXXXXXXXXX = "XXXXXXXXXXXXX"; YYYYYYYYYYYY = "YYYYYYYYYYYYYYY" } +""" config + |> prepend newline + |> should equal """ +let configurations = + buildConfiguration + { XXXXXXXXXXXX = "XXXXXXXXXXXXX" + YYYYYYYYYYYY = "YYYYYYYYYYYYYYY" } +""" + +[] +let ``records in list`` () = + formatSourceString false """let configurations = + [ + { Build = true; Configuration = "RELEASE"; Defines = ["FOO"] } + { Build = true; Configuration = "DEBUG"; Defines = ["FOO";"BAR"] } + { Build = true; Configuration = "UNKNOWN"; Defines = [] } + ] +""" config + |> prepend newline + |> should equal """ +let configurations = + [ { Build = true + Configuration = "RELEASE" + Defines = [ "FOO" ] } + { Build = true + Configuration = "DEBUG" + Defines = [ "FOO"; "BAR" ] } + { Build = true + Configuration = "UNKNOWN" + Defines = [] } ] +""" + +[] +let ``anonymous records in list`` () = + formatSourceString false """let configurations = + [ + {| Build = true; Configuration = "RELEASE"; Defines = ["FOO"] |} + {| Build = true; Configuration = "DEBUG"; Defines = ["FOO";"BAR"] |} + ] +""" config + |> prepend newline + |> should equal """ +let configurations = + [ {| Build = true + Configuration = "RELEASE" + Defines = [ "FOO" ] |} + {| Build = true + Configuration = "DEBUG" + Defines = [ "FOO"; "BAR" ] |} ] +""" + +[] +let ``records in array`` () = + formatSourceString false """let configurations = + [| + { Build = true; Configuration = "RELEASE"; Defines = ["FOO"] } + { Build = true; Configuration = "DEBUG"; Defines = ["FOO";"BAR"] } + |] +""" config + |> prepend newline + |> should equal """ +let configurations = + [| { Build = true + Configuration = "RELEASE" + Defines = [ "FOO" ] } + { Build = true + Configuration = "DEBUG" + Defines = [ "FOO"; "BAR" ] } |] +""" + +[] +let ``object expression`` () = + formatSourceString false """ +let obj1 = { new System.Object() with member x.ToString() = "F#" } +""" config + |> prepend newline + |> should equal """ +let obj1 = + { new System.Object() with + member x.ToString() = "F#" } +""" + +// FIXME: See https://github.com/fsprojects/fantomas/issues/1170 +[] +[] +let ``object expressions in list, 1170`` () = + formatSourceString false """ +let a = + [ + { new System.Object() with member x.ToString() = "F#" } + { new System.Object() with member x.ToString() = "C#" } + ] +""" config + |> prepend newline + |> should equal """ +let a = + [ { new System.Object() with + member x.ToString() = "F#" } + { new System.Object() with + member x.ToString() = "C#" } ] +""" + +[] +let ``record type signature with bracketOnSeparateLine`` () = + formatSourceString true """ +module RecordSignature +/// Represents simple XML elements. +type Element = + { + /// The attribute collection. + Attributes: IDictionary; + + /// The children collection. + Children: seq; + + /// The qualified name. + Name: Name } +""" config + |> prepend newline + |> should equal """ +module RecordSignature +/// Represents simple XML elements. +type Element = + { + /// The attribute collection. + Attributes: IDictionary + + /// The children collection. + Children: seq + + /// The qualified name. + Name: Name } +""" + +[] +let ``record type with member definitions should align with bracket`` () = + formatSourceString false """ +type Range = + { From: float + To: float } + member this.Length = this.To - this.From +""" + { config with + MaxValueBindingWidth = 120 } + |> prepend newline + |> should equal """ +type Range = + { From: float + To: float } + member this.Length = this.To - this.From +""" + +[] +let ``record type with interface`` () = + formatSourceString false """ +type MyRecord = + { SomeField : int + } + interface IMyInterface +""" config + |> prepend newline + |> should equal """ +type MyRecord = + { SomeField: int } + interface IMyInterface +""" + +[] +[] +let ``SynPat.Record in pattern match, 1173`` () = + formatSourceString false """match foo with +| { Bar = bar; Level = 12; Vibes = plenty; Lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " } -> "7" +| _ -> "8" +""" config + |> prepend newline + |> should equal """ +match foo with +| { Bar = bar + Level = 12 + Vibes = plenty + Lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " } -> + "7" +| _ -> "8" +""" + +[] +let ``record declaration`` () = + formatSourceString false """type MyRecord = + { Level: int + Progress: string + Bar: string + Street: string + Number: int } +""" config + |> prepend newline + |> should equal """ +type MyRecord = + { Level: int + Progress: string + Bar: string + Street: string + Number: int } +""" + +[] +let ``record declaration in signature file`` () = + formatSourceString true """namespace X +type MyRecord = + { Level: int + Progress: string + Bar: string + Street: string + Number: int } +""" config + |> prepend newline + |> should equal """ +namespace X + +type MyRecord = + { Level: int + Progress: string + Bar: string + Street: string + Number: int } +""" + +[] +let ``record declaration with members in signature file`` () = + formatSourceString true """namespace X +type MyRecord = + { Level: int + Progress: string + Bar: string + Street: string + Number: int } + member Score : unit -> int +""" config + |> prepend newline + |> should equal """ +namespace X + +type MyRecord = + { Level: int + Progress: string + Bar: string + Street: string + Number: int } + member Score: unit -> int +""" + + +[] +let ``no newline before first multiline member`` () = + formatSourceString false """ +type ShortExpressionInfo = + { MaxWidth: int + StartColumn: int + ConfirmedMultiline: bool } + member x.IsTooLong maxPageWidth currentColumn = + currentColumn - x.StartColumn > x.MaxWidth // expression is not too long according to MaxWidth + || (currentColumn > maxPageWidth) // expression at current position is not going over the page width + member x.Foo() = () +""" + ({ config with + NewlineBetweenTypeDefinitionAndMembers = false }) + |> prepend newline + |> should equal """ +type ShortExpressionInfo = + { MaxWidth: int + StartColumn: int + ConfirmedMultiline: bool } + member x.IsTooLong maxPageWidth currentColumn = + currentColumn - x.StartColumn > x.MaxWidth // expression is not too long according to MaxWidth + || (currentColumn > maxPageWidth) // expression at current position is not going over the page width + + member x.Foo() = () +""" + +// FIXME: See https://github.com/fsprojects/fantomas/issues/1171 +[] +[] +let ``internal keyword before multiline record type, 1171`` () = + formatSourceString false """ + type A = internal { ALongIdentifier: string; YetAnotherLongIdentifier: bool }""" config + |> prepend newline + |> should equal """ +type A = + internal + { ALongIdentifier: string + YetAnotherLongIdentifier: bool } +""" + +// FIXME: See https://github.com/fsprojects/fantomas/issues/1171 +[] +[] +let ``internal keyword before multiline record type in signature file, 1171`` () = + formatSourceString true """namespace Bar + + type A = internal { ALongIdentifier: string; YetAnotherLongIdentifier: bool }""" config + |> prepend newline + |> should equal """ +namespace Bar + +type A = + internal + { ALongIdentifier: string + YetAnotherLongIdentifier: bool } +""" + +[] +let ``indent update record fields far enough, 817`` () = + formatSourceString + false + "let expected = { ThisIsAThing.Empty with TheNewValue = 1; ThatValue = 2 }" + ({ config with IndentSize = 2 }) + |> prepend newline + |> should equal """ +let expected = + { ThisIsAThing.Empty with + TheNewValue = 1 + ThatValue = 2 } +""" + +[] +let ``indent update anonymous record fields far enough`` () = + formatSourceString + false + "let expected = {| ThisIsAThing.Empty with TheNewValue = 1; ThatValue = 2 |}" + ({ config with IndentSize = 2 }) + |> prepend newline + |> should equal """ +let expected = + {| ThisIsAThing.Empty with + TheNewValue = 1 + ThatValue = 2 |} +""" + +[] +let ``update record with standard indent`` () = + formatSourceString false "let expected = { ThisIsAThing.Empty with TheNewValue = 1; ThatValue = 2 }" config + |> prepend newline + |> should equal """ +let expected = + { ThisIsAThing.Empty with + TheNewValue = 1 + ThatValue = 2 } +""" + +[] +let ``record type with attributes`` () = + formatSourceString false """ +[] +type Args = + { [] + [] + [] + Hi: int list } + +module Foo = + + let r = 3 +""" config + |> prepend newline + |> should equal """ +[] +type Args = + { [] + [] + [] + Hi: int list } + +module Foo = + + let r = 3 +""" + +[] +let ``comment before access modifier of record type declaration`` () = + formatSourceString false """ +type TestType = + // Here is some comment about the type + // Some more comments + private + { + Foo : int + } +""" config + |> prepend newline + |> should equal """ +type TestType = + // Here is some comment about the type + // Some more comments + private { Foo: int } +""" + +[] +let ``defines in record assignment, 968`` () = + formatSourceString false """ +let config = { + title = "Fantomas" + description = "Fantomas is a code formatter for F#" + theme_variant = Some "red" + root_url = + #if WATCH + "http://localhost:8080/" + #else + "https://fsprojects.github.io/fantomas/" + #endif +} +""" config + |> prepend newline + |> should equal """ +let config = + { title = "Fantomas" + description = "Fantomas is a code formatter for F#" + theme_variant = Some "red" + root_url = +#if WATCH + "http://localhost:8080/" +#else + "https://fsprojects.github.io/fantomas/" +#endif + } +""" + +// FIXME: See https://github.com/fsprojects/fantomas/issues/1172 +[] +[] +let ``comment after closing brace in nested record, 1172`` () = + formatSourceString false """ +let person = + { Name = "James" + Address = { Street = "Bakerstreet"; Number = 42 } // end address + } // end person +""" config + |> prepend newline + |> should equal """ +let person = + { Name = "James" + Address = + { Street = "Bakerstreet" + Number = 42 } // end address + } // end person +""" + +[] +let ``number of items sized record definitions are formatted properly`` () = + formatSourceString false """ +type R = { a: int; b: string; c: float option } +type S = { AReallyLongExpressionThatIsMuchLongerThan50Characters: int } + """ + { config with + RecordMultilineFormatter = NumberOfItems } + |> prepend newline + |> should equal """ +type R = + { a: int + b: string + c: float option } + +type S = { AReallyLongExpressionThatIsMuchLongerThan50Characters: int } +""" + +[] +let ``number of items sized record definitions with multiline block brackets on same column are formatted properly`` () = + formatSourceString false """ +type R = { a: int; b: string; c: float option } +type S = { AReallyLongExpressionThatIsMuchLongerThan50Characters: int } + """ + { config with + RecordMultilineFormatter = NumberOfItems + MultilineBlockBracketsOnSameColumn = true } + |> prepend newline + |> should equal """ +type R = + { + a: int + b: string + c: float option + } + +type S = { AReallyLongExpressionThatIsMuchLongerThan50Characters: int } +""" + +[] +let ``number of items sized record expressions are formatted properly`` () = + formatSourceString false """ +let r = { a = x; b = y; z = c } +let s = { AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +let r' = { r with a = x; b = y; z = c } +let s' = { s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +f r { a = x; b = y; z = c } +g s { AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +f r' { r with a = x; b = y; z = c } +g s' { s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + """ + { config with + RecordMultilineFormatter = NumberOfItems } + |> prepend newline + |> should equal """ +let r = + { a = x + b = y + z = c } + +let s = + { AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +let r' = + { r with + a = x + b = y + z = c } + +let s' = + { s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +f + r + { a = x + b = y + z = c } + +g s { AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +f + r' + { r with + a = x + b = y + z = c } + +g s' { s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } +""" + +[] +let ``number of items sized record expressions with multiline block brackets on same column are formatted properly`` () = + formatSourceString false """ +let r = { a = x; b = y; z = c } +let s = { AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +let r' = { r with a = x; b = y; z = c } +let s' = { s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +f r { a = x; b = y; z = c } +g s { AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +f r' { r with a = x; b = y; z = c } +g s' { s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + """ + { config with + RecordMultilineFormatter = NumberOfItems + MultilineBlockBracketsOnSameColumn = true } + |> prepend newline + |> should equal """ +let r = + { + a = x + b = y + z = c + } + +let s = + { AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +let r' = + { r with + a = x + b = y + z = c + } + +let s' = + { s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +f + r + { + a = x + b = y + z = c + } + +g s { AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } + +f + r' + { r with + a = x + b = y + z = c + } + +g s' { s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 } +""" + +[] +let ``number of items sized anonymous record expressions are formatted properly`` () = + formatSourceString false """ +let r = {| a = x; b = y; z = c |} +let s = {| AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +let r' = {| r with a = x; b = y; z = c |} +let s' = {| s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +f r {| a = x; b = y; z = c |} +g s {| AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +f r' {| r with a = x; b = y; z = c |} +g s' {| s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + """ + { config with + RecordMultilineFormatter = NumberOfItems } + |> prepend newline + |> should equal """ +let r = + {| a = x + b = y + z = c |} + +let s = + {| AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +let r' = + {| r with + a = x + b = y + z = c |} + +let s' = + {| s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +f + r + {| a = x + b = y + z = c |} + +g s {| AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +f + r' + {| r with + a = x + b = y + z = c |} + +g s' {| s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} +""" + +[] +let ``number of items sized anonymous record expressions with multiline block brackets on same column are formatted properly`` () = + formatSourceString false """ +let r = {| a = x; b = y; z = c |} +let s = {| AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +let r' = {| r with a = x; b = y; z = c |} +let s' = {| s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +f r {| a = x; b = y; z = c |} +g s {| AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +f r' {| r with a = x; b = y; z = c |} +g s' {| s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + """ + { config with + RecordMultilineFormatter = NumberOfItems + MultilineBlockBracketsOnSameColumn = true } + |> prepend newline + |> should equal """ +let r = + {| + a = x + b = y + z = c + |} + +let s = + {| AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +let r' = + {| r with + a = x + b = y + z = c + |} + +let s' = + {| s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +f + r + {| + a = x + b = y + z = c + |} + +g s {| AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} + +f + r' + {| r with + a = x + b = y + z = c + |} + +g s' {| s with AReallyLongExpressionThatIsMuchLongerThan50Characters = 1 |} +""" + +[] +let ``number of items sized anonymous record types are formatted properly`` () = + formatSourceString false """ +let f (x: {| x: int; y: obj |}) = x +let g (x: {| x: AReallyLongTypeThatIsMuchLongerThan40Characters |}) = x +type A = {| x: int; y: obj |} +type B = {| x: AReallyLongTypeThatIsMuchLongerThan40Characters |} +""" + { config with + RecordMultilineFormatter = NumberOfItems } + |> prepend newline + |> should equal """ +let f (x: {| x: int + y: obj |}) = + x + +let g (x: {| x: AReallyLongTypeThatIsMuchLongerThan40Characters |}) = x + +type A = + {| x: int + y: obj |} + +type B = {| x: AReallyLongTypeThatIsMuchLongerThan40Characters |} +""" + +// FIXME: See https://github.com/fsprojects/fantomas/issues/1167 +[] +[] +let ``number of items sized anonymous record types with multiline block brackets on same column are formatted properly`` () = + formatSourceString false """ +let f (x: {| x: int; y: obj |}) = x +let g (x: {| x: AReallyLongTypeThatIsMuchLongerThan40Characters |}) = x +type A = {| x: int; y: obj |} +type B = {| x: AReallyLongTypeThatIsMuchLongerThan40Characters |} +""" + { config with + RecordMultilineFormatter = NumberOfItems + MultilineBlockBracketsOnSameColumn = true } + |> prepend newline + |> should equal """ + +let f (x: {| + x : int + y : AReallyLongTypeThatIsMuchLongerThan40Characters + |}) = + x +let g (x: {| x: AReallyLongTypeThatIsMuchLongerThan40Characters |}) = x + +type A = + {| + x: int + y: AReallyLongTypeThatIsMuchLongerThan40Characters + |} + +type B = {| x: AReallyLongTypeThatIsMuchLongerThan40Characters |} +""" diff --git a/src/Fantomas/CodePrinter.fs b/src/Fantomas/CodePrinter.fs index 9dc83db418..f2fed9953c 100644 --- a/src/Fantomas/CodePrinter.fs +++ b/src/Fantomas/CodePrinter.fs @@ -1417,7 +1417,7 @@ and genExpr astContext synExpr = isSmallExpression size smallExpression multilineExpression ctx | Record (inheritOpt, xs, eo) -> - let shortRecordExpr = + let smallRecordExpr = sepOpenS +> leaveLeftBrace synExpr.Range +> optSingle (fun (inheritType, inheritExpr) -> @@ -1435,10 +1435,12 @@ and genExpr astContext synExpr = (genMultilineRecordInstanceAlignBrackets inheritOpt xs eo synExpr astContext) (genMultilineRecordInstance inheritOpt xs eo synExpr astContext) - fun ctx -> isShortExpression ctx.Config.MaxRecordWidth shortRecordExpr multilineRecordExpr ctx + fun ctx -> + let size = getRecordSize ctx xs + isSmallExpression size smallRecordExpr multilineRecordExpr ctx | AnonRecord (isStruct, fields, copyInfo) -> - let shortExpression = + let smallExpression = onlyIf isStruct !- "struct " +> sepOpenAnonRecd +> optSingle (fun e -> genExpr astContext e +> !- " with ") copyInfo @@ -1450,7 +1452,9 @@ and genExpr astContext synExpr = (genMultilineAnonRecordAlignBrackets isStruct fields copyInfo astContext) (genMultilineAnonRecord isStruct fields copyInfo astContext) - fun (ctx: Context) -> isShortExpression ctx.Config.MaxRecordWidth shortExpression longExpression ctx + fun (ctx: Context) -> + let size = getRecordSize ctx fields + isSmallExpression size smallExpression longExpression ctx | ObjExpr (t, eio, bd, ims, range) -> ifAlignBrackets @@ -3093,7 +3097,7 @@ and genTypeDefn astContext (TypeDef (ats, px, ao, tds, tcs, tdr, ms, s, preferPo +> unindent | Simple (TDSRRecord (ao', fs)) -> - let shortExpression = + let smallExpression = sepSpace +> optSingle (fun ao -> genAccess ao +> sepSpace) ao' +> sepOpenS @@ -3108,11 +3112,13 @@ and genTypeDefn astContext (TypeDef (ats, px, ao, tds, tcs, tdr, ms, s, preferPo (genMultilineSimpleRecordTypeDefn tdr ms ao' fs astContext) let bodyExpr ctx = + let size = getRecordSize ctx fs + if (List.isEmpty ms) then - (isShortExpression - ctx.Config.MaxRecordWidth + (isSmallExpression + size (enterNodeFor SynTypeDefnSimpleRepr_Record tdr.Range - +> shortExpression) + +> smallExpression) multilineExpression +> leaveNodeFor SynTypeDefnSimpleRepr_Record tdr.Range // this will only print something when there is trivia after } in the short expression // Yet it cannot be part of the short expression otherwise the multiline expression would be triggered unwillingly. @@ -3371,7 +3377,6 @@ and genSigTypeDefn astContext (SigTypeDef (ats, px, ao, tds, tcs, tdr, ms, s, pr +> unindent | SigSimple (TDSRUnion (ao', xs)) -> - let unionCases = match xs with | [] -> id @@ -3411,7 +3416,7 @@ and genSigTypeDefn astContext (SigTypeDef (ats, px, ao, tds, tcs, tdr, ms, s, pr +> unindent | SigSimple (TDSRRecord (ao', fs)) -> - let shortExpr = + let smallExpr = typeName +> sepEq +> sepSpace @@ -3427,9 +3432,11 @@ and genSigTypeDefn astContext (SigTypeDef (ats, px, ao, tds, tcs, tdr, ms, s, pr (genSigSimpleRecord typeName tdr ms ao' fs astContext) fun ctx -> + let size = getRecordSize ctx fs + if List.isNotEmpty ms then longExpr ctx - else isShortExpression ctx.Config.MaxRecordWidth shortExpr longExpr ctx + else isSmallExpression size smallExpr longExpr ctx | SigSimple TDSRNone -> let genMembers = @@ -3749,7 +3756,7 @@ and genType astContext outerBracket t = && astContext.IsCStylePattern) (genTypeByLookup astContext t) (!-s) |> genTriviaFor Ident_ current.Range | TAnonRecord (isStruct, fields) -> - let shortExpression = + let smallExpression = ifElse isStruct !- "struct " sepNone +> sepOpenAnonRecd +> col sepSemi fields (genAnonRecordFieldType astContext) @@ -3761,7 +3768,9 @@ and genType astContext outerBracket t = +> atCurrentColumn (col sepSemiNln fields (genAnonRecordFieldType astContext)) +> sepCloseAnonRecd - fun (ctx: Context) -> isShortExpression ctx.Config.MaxRecordWidth shortExpression longExpression ctx + fun (ctx: Context) -> + let size = getRecordSize ctx fields + isSmallExpression size smallExpression longExpression ctx | TParen (innerT) -> sepOpenT +> loop innerT diff --git a/src/Fantomas/Context.fs b/src/Fantomas/Context.fs index 48752a5ada..e9478afbba 100644 --- a/src/Fantomas/Context.fs +++ b/src/Fantomas/Context.fs @@ -527,6 +527,11 @@ let internal getListOrArrayExprSize ctx maxWidth xs = | MultilineFormatterType.CharacterWidth -> Size.CharacterWidth maxWidth | MultilineFormatterType.NumberOfItems -> Size.NumberOfItems(List.length xs, ctx.Config.MaxArrayOrListNumberOfItems) +let internal getRecordSize ctx fields = + match ctx.Config.RecordMultilineFormatter with + | MultilineFormatterType.CharacterWidth -> Size.CharacterWidth ctx.Config.MaxRecordWidth + | MultilineFormatterType.NumberOfItems -> Size.NumberOfItems(List.length fields, ctx.Config.MaxRecordNumberOfItems) + /// b is true, apply f1 otherwise apply f2 let internal ifElse b (f1: Context -> Context) f2 (ctx: Context) = if b then f1 ctx else f2 ctx diff --git a/src/Fantomas/FormatConfig.fs b/src/Fantomas/FormatConfig.fs index 8e07b59c47..cc99c2b31b 100644 --- a/src/Fantomas/FormatConfig.fs +++ b/src/Fantomas/FormatConfig.fs @@ -44,6 +44,8 @@ type FormatConfig = MaxIfThenElseShortWidth: Num MaxInfixOperatorExpression: Num MaxRecordWidth: Num + MaxRecordNumberOfItems: Num + RecordMultilineFormatter: MultilineFormatterType MaxArrayOrListWidth: Num MaxArrayOrListNumberOfItems: Num ArrayOrListMultilineFormatter: MultilineFormatterType @@ -78,6 +80,8 @@ type FormatConfig = MaxIfThenElseShortWidth = 40 MaxInfixOperatorExpression = 50 MaxRecordWidth = 40 + MaxRecordNumberOfItems = 1 + RecordMultilineFormatter = CharacterWidth MaxArrayOrListWidth = 40 MaxArrayOrListNumberOfItems = 1 ArrayOrListMultilineFormatter = CharacterWidth