From ed18f75f824218a8fad2e38b01ae4f74b44f0241 Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 18 Sep 2020 16:58:44 +0200 Subject: [PATCH 1/4] First implementation for MaxDotGetExpressionWidth setting. --- src/Fantomas.Tests/CompilerDirectivesTests.fs | 28 ++-- .../ComputationExpressionTests.fs | 6 +- src/Fantomas.Tests/DotGetTests.fs | 59 +++++++- src/Fantomas.Tests/FunctionDefinitionTests.fs | 5 +- src/Fantomas.Tests/LambdaTests.fs | 8 +- src/Fantomas.Tests/LetBindingTests.fs | 13 +- src/Fantomas.Tests/LongIdentWithDotsTests.fs | 39 ++++-- src/Fantomas.Tests/OperatorTests.fs | 12 +- src/Fantomas.Tests/QuotationTests.fs | 5 +- src/Fantomas.Tests/RecordTests.fs | 98 ++++++------- src/Fantomas.Tests/TypeDeclarationTests.fs | 8 +- src/Fantomas.Tests/TypeProviderTests.fs | 3 +- src/Fantomas/CodePrinter.fs | 129 ++++++++++-------- src/Fantomas/FormatConfig.fs | 2 + src/Fantomas/SourceParser.fs | 8 ++ src/Fantomas/Utils.fs | 6 + 16 files changed, 287 insertions(+), 142 deletions(-) diff --git a/src/Fantomas.Tests/CompilerDirectivesTests.fs b/src/Fantomas.Tests/CompilerDirectivesTests.fs index f2ce1d6334..368d6a745d 100644 --- a/src/Fantomas.Tests/CompilerDirectivesTests.fs +++ b/src/Fantomas.Tests/CompilerDirectivesTests.fs @@ -323,20 +323,30 @@ let start (args: IArgs) = |> should equal """let start (args: IArgs) = // Serilog configuration Log.Logger <- - LoggerConfiguration().MinimumLevel.Debug().MinimumLevel.Override("Microsoft", LogEventLevel.Information) - .Enrich.FromLogContext().WriteTo.Console().WriteTo.File(Path.Combine(args.ContentRoot, "temp/log.txt")) + LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File(Path.Combine(args.ContentRoot, "temp/log.txt")) .CreateLogger() try try let giraffeApp = configureGiraffeApp args - WebHost.CreateDefaultBuilder().UseWebRoot(args.ClientPath) + WebHost + .CreateDefaultBuilder() + .UseWebRoot(args.ClientPath) #if DEBUG - .UseContentRoot(args.ContentRoot).UseUrls(args.Host + ":" + string args.Port) + .UseContentRoot(args.ContentRoot) + .UseUrls(args.Host + ":" + string args.Port) #endif - .UseSerilog().Configure(Action(configureApp giraffeApp)) - .ConfigureServices(configureServices args).Build().Run() + .UseSerilog() + .Configure(Action(configureApp giraffeApp)) + .ConfigureServices(configureServices args) + .Build() + .Run() 0 with ex -> @@ -441,7 +451,8 @@ type FunctionComponent = let elemType = ReactBindings.React.``lazy`` (fun () -> // React.lazy requires a default export - (importValueDynamic f).``then``(fun x -> createObj [ "default" ==> x ])) + (importValueDynamic f) + .``then``(fun x -> createObj [ "default" ==> x ])) fun props -> ReactElementType.create @@ -645,7 +656,8 @@ type FunctionComponent = let elemType = ReactBindings.React.``lazy`` (fun () -> // React.lazy requires a default export - (importValueDynamic f).``then``(fun x -> createObj [ "default" ==> x ])) + (importValueDynamic f) + .``then``(fun x -> createObj [ "default" ==> x ])) fun props -> ReactElementType.create diff --git a/src/Fantomas.Tests/ComputationExpressionTests.fs b/src/Fantomas.Tests/ComputationExpressionTests.fs index 037017ba61..468330ba8f 100644 --- a/src/Fantomas.Tests/ComputationExpressionTests.fs +++ b/src/Fantomas.Tests/ComputationExpressionTests.fs @@ -861,7 +861,11 @@ let ``let bang + do expression + let + return in ce`` () = |> prepend newline |> should equal """ task { - let! config = manager.GetConfigurationAsync().ConfigureAwait(false) + let! config = + manager + .GetConfigurationAsync() + .ConfigureAwait(false) + parameters.IssuerSigningKeys <- config.SigningKeys let user, _ = diff --git a/src/Fantomas.Tests/DotGetTests.fs b/src/Fantomas.Tests/DotGetTests.fs index 745335ecc2..7d860aedf6 100644 --- a/src/Fantomas.Tests/DotGetTests.fs +++ b/src/Fantomas.Tests/DotGetTests.fs @@ -11,7 +11,8 @@ Microsoft.FSharp.Reflection.FSharpType.GetUnionCases(typeof> """ config |> prepend newline |> should equal """ -Microsoft.FSharp.Reflection.FSharpType.GetUnionCases(typeof>>.GetGenericTypeDefinition() +Microsoft.FSharp.Reflection.FSharpType.GetUnionCases(typeof>> + .GetGenericTypeDefinition() .MakeGenericType(t)) .Assembly """ @@ -43,7 +44,8 @@ let ``split chained method call expression, 246`` () = root.SetAttribute ("driverVersion", "AltCover.Recorder " - + System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location) + + System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly() + .Location) .FileVersion) """ @@ -100,7 +102,8 @@ module Services = snapshot: (('event -> bool) * ('state -> 'event))) = match storage with | Storage.MemoryStore store -> - Equinox.MemoryStore.Resolver(store, FsCodec.Box.Codec.Create(), fold, initial).Resolve + Equinox.MemoryStore.Resolver(store, FsCodec.Box.Codec.Create(), fold, initial) + .Resolve | Storage.EventStore (gateway, cache) -> let accessStrategy = Equinox.EventStore.AccessStrategy.RollingSnapshots snapshot @@ -116,3 +119,53 @@ module Services = accessStrategy) .Resolve """ + +[] +let ``long chained expression should be multiline, 501`` () = + formatSourceString false """ +module Program + +open Microsoft.AspNetCore.Hosting +open Microsoft.Extensions.Hosting +open Serilog +open Startup + +[] +let main args = + Host + .CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(fun builder -> + builder + .CaptureStartupErrors(true) + .UseSerilog(dispose = true) + .UseStartup() + |> ignore + ) + .Build() + .Run() + 0 +""" config + |> prepend newline + |> should equal """ +module Program + +open Microsoft.AspNetCore.Hosting +open Microsoft.Extensions.Hosting +open Serilog +open Startup + +[] +let main args = + Host + .CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(fun builder -> + builder + .CaptureStartupErrors(true) + .UseSerilog(dispose = true) + .UseStartup() + |> ignore) + .Build() + .Run() + + 0 +""" diff --git a/src/Fantomas.Tests/FunctionDefinitionTests.fs b/src/Fantomas.Tests/FunctionDefinitionTests.fs index 599f3502cb..d86842f259 100644 --- a/src/Fantomas.Tests/FunctionDefinitionTests.fs +++ b/src/Fantomas.Tests/FunctionDefinitionTests.fs @@ -539,7 +539,10 @@ let private addTaskToScheduler (scheduler: IScheduler) jobDataMap.["task"] <- task let job = - JobBuilder.Create().UsingJobData(jobDataMap).WithIdentity(taskName, groupName) + JobBuilder + .Create() + .UsingJobData(jobDataMap) + .WithIdentity(taskName, groupName) .Build() 1 diff --git a/src/Fantomas.Tests/LambdaTests.fs b/src/Fantomas.Tests/LambdaTests.fs index c025620188..3de0adb86d 100644 --- a/src/Fantomas.Tests/LambdaTests.fs +++ b/src/Fantomas.Tests/LambdaTests.fs @@ -253,9 +253,11 @@ CloudStorageAccount.SetConfigurationSettingPublisher(fun configName configSettin |> should equal """ CloudStorageAccount.SetConfigurationSettingPublisher(fun configName configSettingPublisher -> let connectionString = - if hostedService - then RoleEnvironment.GetConfigurationSettingValue(configName) - else ConfigurationManager.ConnectionStrings.[configName].ConnectionString + if hostedService then + RoleEnvironment.GetConfigurationSettingValue(configName) + else + ConfigurationManager.ConnectionStrings.[configName] + .ConnectionString configSettingPublisher.Invoke(connectionString) |> ignore) diff --git a/src/Fantomas.Tests/LetBindingTests.fs b/src/Fantomas.Tests/LetBindingTests.fs index 6c2daa013c..a31e8ea901 100644 --- a/src/Fantomas.Tests/LetBindingTests.fs +++ b/src/Fantomas.Tests/LetBindingTests.fs @@ -97,7 +97,9 @@ let tomorrow = MaxValueBindingWidth = 70 }) |> prepend newline |> should equal """ -let tomorrow = DateTimeOffset(n.Year, n.Month, n.Day, 0, 0, 0, n.Offset).AddDays(1.) +let tomorrow = + DateTimeOffset(n.Year, n.Month, n.Day, 0, 0, 0, n.Offset) + .AddDays(1.) """ [] @@ -721,7 +723,11 @@ let private authenticateRequest (logger: ILogger) header = try task { - let! config = manager.GetConfigurationAsync().ConfigureAwait(false) + let! config = + manager + .GetConfigurationAsync() + .ConfigureAwait(false) + parameters.IssuerSigningKeys <- config.SigningKeys let user, _ = @@ -874,7 +880,8 @@ let ``don't add additional newline before SynExpr.New, 1049`` () = let getVersion () = let version = let assembly = - typeof.Assembly + typeof + .Assembly let version = assembly.GetName().Version sprintf "%i.%i.%i" version.Major version.Minor version.Revision diff --git a/src/Fantomas.Tests/LongIdentWithDotsTests.fs b/src/Fantomas.Tests/LongIdentWithDotsTests.fs index cf755dc33d..d76fbfd794 100644 --- a/src/Fantomas.Tests/LongIdentWithDotsTests.fs +++ b/src/Fantomas.Tests/LongIdentWithDotsTests.fs @@ -5,7 +5,7 @@ open FsUnit open Fantomas.Tests.TestHelper [] -let ``Fluent api should not remain on the same lines`` () = +let ``fluent api should not remain on the same lines`` () = formatSourceString false """ Log.Logger <- LoggerConfiguration() @@ -14,7 +14,11 @@ Log.Logger <- .CreateLogger()""" config |> prepend newline |> should equal """ -Log.Logger <- LoggerConfiguration().Destructure.FSharpTypes().WriteTo.Console().CreateLogger() +Log.Logger <- + LoggerConfiguration() + .Destructure.FSharpTypes() + .WriteTo.Console() + .CreateLogger() """ [] @@ -106,13 +110,20 @@ let main _ = Config.Logger.configure () let config = - ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).Build() + ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .Build() - WebHostBuilder().UseConfiguration(config).UseKestrel().UseSerilog() + WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseSerilog() .ConfigureAppConfiguration(Action configureAppConfiguration) .ConfigureServices(Action configureServices) - .Configure(Action configureApp).Build().Run() + .Configure(Action configureApp) + .Build() + .Run() |> ignore 0 @@ -187,19 +198,21 @@ module Client = let passwordValid = Var.Create true let emailValid = Var.Create true - MySPA().AttrPassword(Attr.ClassPred "is-danger" (not passwordValid.V)) + MySPA() + .AttrPassword(Attr.ClassPred "is-danger" (not passwordValid.V)) .AttrEmail(Attr.ClassPred "is-danger" (not emailValid.V)) .Login(fun e -> - passwordValid - := not (String.IsNullOrWhiteSpace e.Vars.Password.Value) + passwordValid + := not (String.IsNullOrWhiteSpace e.Vars.Password.Value) - emailValid - := not (String.IsNullOrWhiteSpace e.Vars.Email.Value) + emailValid + := not (String.IsNullOrWhiteSpace e.Vars.Email.Value) - if passwordValid.Value && emailValid.Value - then JS.Alert(sprintf "Your email is %s" e.Vars.Email.Value) + if passwordValid.Value && emailValid.Value + then JS.Alert(sprintf "Your email is %s" e.Vars.Email.Value) - e.Event.PreventDefault()).Bind() + e.Event.PreventDefault()) + .Bind() """ [] diff --git a/src/Fantomas.Tests/OperatorTests.fs b/src/Fantomas.Tests/OperatorTests.fs index 97b9cf100c..1521b79ced 100644 --- a/src/Fantomas.Tests/OperatorTests.fs +++ b/src/Fantomas.Tests/OperatorTests.fs @@ -94,9 +94,15 @@ let ``should break on . operator`` () = """ { config with MaxLineLength = 80 } |> prepend newline |> should equal """ -pattern.Replace(".", @"\.").Replace("$", @"\$").Replace("^", @"\^") - .Replace("{", @"\{").Replace("[", @"\[").Replace("(", @"\(") - .Replace(")", @"\)").Replace("+", @"\+") +pattern + .Replace(".", @"\.") + .Replace("$", @"\$") + .Replace("^", @"\^") + .Replace("{", @"\{") + .Replace("[", @"\[") + .Replace("(", @"\(") + .Replace(")", @"\)") + .Replace("+", @"\+") """ // the current behavior results in a compile error since line break is before the parens and not before the . diff --git a/src/Fantomas.Tests/QuotationTests.fs b/src/Fantomas.Tests/QuotationTests.fs index 2029c12cd8..6bc00b5da6 100644 --- a/src/Fantomas.Tests/QuotationTests.fs +++ b/src/Fantomas.Tests/QuotationTests.fs @@ -27,5 +27,8 @@ let ``untyped quotations`` () = let ``should preserve unit literal`` () = shouldNotChangeAfterFormat """ let logger = - Mock().Setup(fun log -> <@ log.Log(error) @>).Returns(()).Create() + Mock() + .Setup(fun log -> <@ log.Log(error) @>) + .Returns(()) + .Create() """ diff --git a/src/Fantomas.Tests/RecordTests.fs b/src/Fantomas.Tests/RecordTests.fs index 5e27e1f8e2..10f4c3f133 100644 --- a/src/Fantomas.Tests/RecordTests.fs +++ b/src/Fantomas.Tests/RecordTests.fs @@ -352,9 +352,9 @@ let ``meaningful space should be preserved, 353`` () = |> prepend newline |> should equal """ to'.WithCommon(fun o' -> - { dotnetOptions o' with - WorkingDirectory = Path.getFullName "RegressionTesting/issue29" - Verbosity = Some DotNet.Verbosity.Minimal }) + { dotnetOptions o' with + WorkingDirectory = Path.getFullName "RegressionTesting/issue29" + Verbosity = Some DotNet.Verbosity.Minimal }) .WithParameters """ @@ -442,25 +442,28 @@ I wanted to know why you created Fable. Did you always plan to use F#? Or were y Logger.debug \"Database restored\" " config - |> should equal "type Database = + |> prepend newline + |> should equal " +type Database = static member Default() = - Database.Lowdb.defaults({ Version = CurrentVersion - Questions = - [| { Id = 0 - AuthorId = 1 - Title = \"What is the average wing speed of an unladen swallow?\" - Description = \"\"\" + Database + .Lowdb.defaults({ Version = CurrentVersion + Questions = + [| { Id = 0 + AuthorId = 1 + Title = \"What is the average wing speed of an unladen swallow?\" + Description = \"\"\" Hello, yesterday I saw a flight of swallows and was wondering what their **average wing speed** is? If you know the answer please share it. \"\"\" - Answers = - [| { Id = 0 - CreatedAt = DateTime.Parse \"2017-09-14T19:57:33.103Z\" - AuthorId = 0 - Score = 2 - Content = \"\"\" + Answers = + [| { Id = 0 + CreatedAt = DateTime.Parse \"2017-09-14T19:57:33.103Z\" + AuthorId = 0 + Score = 2 + Content = \"\"\" > What do you mean, an African or European Swallow? > > Monty Python’s: The Holy Grail @@ -468,12 +471,12 @@ If you know the answer please share it. Ok I must admit, I use google to search the question and found a post explaining the reference :). I thought you were asking it seriously, well done. - x\"\"\" } - { Id = 1 - CreatedAt = DateTime.Parse \"2017-09-14T20:07:27.103Z\" - AuthorId = 2 - Score = 1 - Content = \"\"\" + x\"\"\" } + { Id = 1 + CreatedAt = DateTime.Parse \"2017-09-14T20:07:27.103Z\" + AuthorId = 2 + Score = 1 + Content = \"\"\" Maxime, I believe you found [this blog post](http://www.saratoga.com/how-should-i-know/2013/07/what-is-the-average-air-speed-velocity-of-a-laden-swallow/). @@ -481,35 +484,36 @@ I believe you found [this blog post](http://www.saratoga.com/how-should-i-know/2 And so Robin, the conclusion of the post is: > In the end, it’s concluded that the airspeed velocity of a (European) unladen swallow is about 24 miles per hour or 11 meters per second. - \"\"\" } |] - CreatedAt = DateTime.Parse \"2017-09-14T17:44:28.103Z\" } - { Id = 1 - AuthorId = 0 - Title = \"Why did you create Fable?\" - Description = \"\"\" + \"\"\" } |] + CreatedAt = DateTime.Parse \"2017-09-14T17:44:28.103Z\" } + { Id = 1 + AuthorId = 0 + Title = \"Why did you create Fable?\" + Description = \"\"\" Hello Alfonso, I wanted to know why you created Fable. Did you always plan to use F#? Or were you thinking in others languages? \"\"\" - Answers = [||] - CreatedAt = DateTime.Parse \"2017-09-12T09:27:28.103Z\" } |] - Users = - [| { Id = 0 - Firstname = \"Maxime\" - Surname = \"Mangel\" - Avatar = \"maxime_mangel.png\" } - { Id = 1 - Firstname = \"Robin\" - Surname = \"Munn\" - Avatar = \"robin_munn.png\" } - { Id = 2 - Firstname = \"Alfonso\" - Surname = \"Garciacaro\" - Avatar = \"alfonso_garciacaro.png\" } - { Id = 3 - Firstname = \"Guest\" - Surname = \"\" - Avatar = \"guest.png\" } |] }).write() + Answers = [||] + CreatedAt = DateTime.Parse \"2017-09-12T09:27:28.103Z\" } |] + Users = + [| { Id = 0 + Firstname = \"Maxime\" + Surname = \"Mangel\" + Avatar = \"maxime_mangel.png\" } + { Id = 1 + Firstname = \"Robin\" + Surname = \"Munn\" + Avatar = \"robin_munn.png\" } + { Id = 2 + Firstname = \"Alfonso\" + Surname = \"Garciacaro\" + Avatar = \"alfonso_garciacaro.png\" } + { Id = 3 + Firstname = \"Guest\" + Surname = \"\" + Avatar = \"guest.png\" } |] }) + .write() Logger.debug \"Database restored\" " diff --git a/src/Fantomas.Tests/TypeDeclarationTests.fs b/src/Fantomas.Tests/TypeDeclarationTests.fs index c5ba7cd2d9..d62f67b4c6 100644 --- a/src/Fantomas.Tests/TypeDeclarationTests.fs +++ b/src/Fantomas.Tests/TypeDeclarationTests.fs @@ -625,9 +625,11 @@ type BlobHelper(Account: CloudStorageAccount) = new(configurationSettingName, hostedService) = CloudStorageAccount.SetConfigurationSettingPublisher(fun configName configSettingPublisher -> let connectionString = - if hostedService - then RoleEnvironment.GetConfigurationSettingValue(configName) - else ConfigurationManager.ConnectionStrings.[configName].ConnectionString + if hostedService then + RoleEnvironment.GetConfigurationSettingValue(configName) + else + ConfigurationManager.ConnectionStrings.[configName] + .ConnectionString configSettingPublisher.Invoke(connectionString) |> ignore) diff --git a/src/Fantomas.Tests/TypeProviderTests.fs b/src/Fantomas.Tests/TypeProviderTests.fs index c80b72daa8..f5444e6c49 100644 --- a/src/Fantomas.Tests/TypeProviderTests.fs +++ b/src/Fantomas.Tests/TypeProviderTests.fs @@ -66,7 +66,8 @@ let ``should handle lines with more than 512 characters`` () = "", '"', true, - false)).Cache() + false)) + .Cache() """ [] diff --git a/src/Fantomas/CodePrinter.fs b/src/Fantomas/CodePrinter.fs index ac9f2c0f7f..d850334217 100644 --- a/src/Fantomas/CodePrinter.fs +++ b/src/Fantomas/CodePrinter.fs @@ -1605,7 +1605,9 @@ and genExpr astContext synExpr = +> autoIndentAndNlnIfExpressionExceedsPageWidth (genExpr astContext e) | Paren (lpr, Lambda (e, sps), rpr) -> fun (ctx: Context) -> - let lastLineOnlyContainsParenthesis = lastLineOnlyContains [| ' '; '(' |] ctx + let addIndent = + lastLineOnlyContains [| ' '; '(' |] ctx + || astContext.IsInsideDotGet let hasLineCommentAfterArrow = findTriviaTokenFromName RARROW synExpr.Range ctx @@ -1620,8 +1622,12 @@ and genExpr astContext synExpr = hasLineCommentAfterArrow (genExpr astContext e) (ifElse - lastLineOnlyContainsParenthesis - (autoIndentAndNlnIfExpressionExceedsPageWidth (genExpr astContext e)) + addIndent + (autoIndentAndNlnIfExpressionExceedsPageWidth + (genExpr + { astContext with + IsInsideDotGet = false } + e)) (autoNlnIfExpressionExceedsPageWidth (genExpr astContext e))) +> sepCloseTFor rpr @@ -1783,12 +1789,28 @@ and genExpr astContext synExpr = // This filters a few long examples of App | DotGetAppSpecial (s, es) -> - !-s - +> atCurrentColumn - (colAutoNlnSkip0 sepNone es (fun ((s, r), e) -> - ((!-(sprintf ".%s" s) |> genTriviaFor Ident_ r) - +> ifElse (hasParenthesis e || isArrayOrList e) sepNone sepSpace - +> genExpr astContext e))) + let shortExpr = + !-s + +> atCurrentColumn + (colAutoNlnSkip0 sepNone es (fun ((s, r), e) -> + ((!-(sprintf ".%s" s) |> genTriviaFor Ident_ r) + +> ifElse (hasParenthesis e || isArrayOrList e) sepNone sepSpace + +> genExpr astContext e))) + + let longExpr = + !-s + +> indent + +> sepNln + +> col sepNln es (fun ((s, r), e) -> + (!-(sprintf ".%s" s) |> genTriviaFor Ident_ r) + +> ifElse (hasParenthesis e || isArrayOrList e) sepNone sepSpace + +> genExpr + { astContext with + IsInsideDotGet = true } + e) + +> unindent + + fun ctx -> isShortExpression ctx.Config.MaxDotGetExpressionWidth shortExpr longExpr ctx | DotGetApp (e, es) as appNode -> fun (ctx: Context) -> @@ -1811,8 +1833,18 @@ and genExpr astContext synExpr = selectIdent appNode - let expr = + let expr sep = match e with + | App (TypeApp (LongIdentPieces (lids), ts), [ e2 ]) when (List.moreThanOne lids) -> + !-(List.head lids) + +> indent + +> sep + +> col sep (List.tail lids) (fun s -> !-(sprintf ".%s" s)) + -- "<" + +> col sepComma ts (genType astContext false) + -- ">" + +> genExpr astContext e2 + +> unindent | App (e1, [ e2 ]) -> noNln (genExpr astContext e1 @@ -1820,51 +1852,38 @@ and genExpr astContext synExpr = +> genExpr astContext e2) | _ -> genExpr astContext e - expr - +> indent - +> (col sepNone es (fun ((s, _), e) -> - let genTriviaOfIdent = - dotGetFuncExprIdents - |> List.tryFind (fun (er, _) -> er = e.Range) - |> Option.map - (snd - >> (fun lid -> genTriviaFor Ident_ lid.idRange)) - |> Option.defaultValue (id) - - let currentIdentifier = genTriviaOfIdent (!-(sprintf ".%s" s)) - let hasParenthesis = hasParenthesis e - - let shortExpr = - (currentIdentifier - +> ifElse hasParenthesis sepNone sepSpace - +> genExpr astContext e) - - let genMultilineExpr = - match e with - | Paren (_, Lambda (_), _) -> atCurrentColumnIndent (genExpr astContext e) - | Paren (_, App (_), _) - | Paren (_, Tuple (_), _) -> atCurrentColumn (genExpr astContext e) - | _ -> - ifElse - hasParenthesis - (indent - +> sepNln - +> genExpr astContext e - +> unindent) - (sepNln +> genExpr astContext e) - - let fallBackExpr = - onlyIf hasParenthesis sepNln - +> currentIdentifier - +> ifElse hasParenthesis sepNone sepSpace - +> expressionFitsOnRestOfLine (genExpr astContext e) genMultilineExpr - - let writeExpr = - expressionFitsOnRestOfLine shortExpr fallBackExpr + let genExpr sep = + let genTriviaOfIdent f (e: SynExpr) = + dotGetFuncExprIdents + |> List.tryFind (fun (er, _) -> er = e.Range) + |> Option.map + (snd + >> (fun lid -> genTriviaFor Ident_ lid.idRange)) + |> Option.defaultValue id + |> fun genTrivia -> genTrivia f + + match es with + | [ (s, _), e ] when (not (hasParenthesis e)) -> + expr sep + +> genTriviaOfIdent (!-(sprintf ".%s" s)) e + +> sepSpaceOrIndentAndNlnIfExpressionExceedsPageWidth (genExpr astContext e) + | _ -> + expr sep + +> indent + +> sep + +> (col sep es (fun ((s, _), e) -> + let currentIdentifier = genTriviaOfIdent (!-(sprintf ".%s" s)) e + let hasParenthesis = hasParenthesis e + + (currentIdentifier + +> ifElse hasParenthesis sepNone sepSpace + +> genExpr + { astContext with + IsInsideDotGet = true } + e))) + +> unindent - writeExpr)) - +> unindent - <| ctx + isShortExpression ctx.Config.MaxDotGetExpressionWidth (genExpr sepNone) (genExpr sepNln) ctx // Unlike infix app, function application needs a level of indentation | App (e1, [ e2 ]) -> @@ -2510,7 +2529,7 @@ and genExpr astContext synExpr = -- (sprintf ".%s" s) +> unindent - expressionFitsOnRestOfLine shortExpr longExpr + fun ctx -> isShortExpression ctx.Config.MaxDotGetExpressionWidth shortExpr longExpr ctx | DotSet (e1, s, e2) -> addParenIfAutoNln e1 (genExpr astContext) -- sprintf ".%s <- " s diff --git a/src/Fantomas/FormatConfig.fs b/src/Fantomas/FormatConfig.fs index e23ca52669..339b1c8731 100644 --- a/src/Fantomas/FormatConfig.fs +++ b/src/Fantomas/FormatConfig.fs @@ -33,6 +33,7 @@ type FormatConfig = MaxArrayOrListWidth: Num MaxValueBindingWidth: Num MaxFunctionBindingWidth: Num + MaxDotGetExpressionWidth: Num MultilineBlockBracketsOnSameColumn: bool NewlineBetweenTypeDefinitionAndMembers: bool KeepIfThenInSameLine: bool @@ -64,6 +65,7 @@ type FormatConfig = MaxArrayOrListWidth = 40 MaxValueBindingWidth = 40 MaxFunctionBindingWidth = 40 + MaxDotGetExpressionWidth = 50 MultilineBlockBracketsOnSameColumn = false NewlineBetweenTypeDefinitionAndMembers = false KeepIfThenInSameLine = false diff --git a/src/Fantomas/SourceParser.fs b/src/Fantomas/SourceParser.fs index 0a309305c9..a3d5470dcc 100644 --- a/src/Fantomas/SourceParser.fs +++ b/src/Fantomas/SourceParser.fs @@ -80,6 +80,14 @@ let (|LongIdent|) (li: LongIdent) = // Assume that if it starts with base, it's going to be the base keyword if String.startsWithOrdinal "``base``." s then String.Join("", "base.", s.[9..]) else s +let (|LongIdentPieces|_|) = + function + | SynExpr.LongIdent (_, LongIdentWithDots (lids, _), _, _) -> + lids + |> List.map (fun x -> if x.idText = MangledGlobalName then "global" else (|Ident|) x) + |> Some + | _ -> None + let inline (|LongIdentWithDots|) (LongIdentWithDots (LongIdent s, _)) = s type Identifier = diff --git a/src/Fantomas/Utils.fs b/src/Fantomas/Utils.fs index 0275ff9fbb..6d672e863a 100644 --- a/src/Fantomas/Utils.fs +++ b/src/Fantomas/Utils.fs @@ -133,6 +133,12 @@ module List = let isNotEmpty l = (List.isEmpty >> not) l + let moreThanOne = + function + | [] + | [ _ ] -> false + | _ -> true + module Map = let tryFindOrDefault (defaultValue: 'g) (key: 't) (map: Map<'t, 'g>) = match Map.tryFind key map with From b5631e293912ff12e7c153374228a46683e87f86 Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 18 Sep 2020 18:14:07 +0200 Subject: [PATCH 2/4] Include TypeApp expressions in nested DotGetApp. --- src/Fantomas.Tests/DotGetTests.fs | 38 ++++++++++++++++ src/Fantomas/CodePrinter.fs | 74 +++++++++++++++++++++---------- src/Fantomas/SourceParser.fs | 9 ++-- 3 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/Fantomas.Tests/DotGetTests.fs b/src/Fantomas.Tests/DotGetTests.fs index 7d860aedf6..34257b541c 100644 --- a/src/Fantomas.Tests/DotGetTests.fs +++ b/src/Fantomas.Tests/DotGetTests.fs @@ -169,3 +169,41 @@ let main args = 0 """ + +[] +let ``nested TypeApp inside DotGet`` () = + formatSourceString false """ +let job = + JobBuilder + .UsingJobData(jobDataMap) + .Create() + .WithIdentity(taskName, groupName) + .Build() +""" config + |> prepend newline + |> should equal """ +let job = + JobBuilder + .UsingJobData(jobDataMap) + .Create() + .WithIdentity(taskName, groupName) + .Build() +""" + +[] +let ``TypeApp at end of nested DotGet`` () = + formatSourceString false """ +let c = + builder + .CaptureStartupErrors(true) + .UseSerilog(dispose = true) + .UseStartup() +""" config + |> prepend newline + |> should equal """ +let c = + builder + .CaptureStartupErrors(true) + .UseSerilog(dispose = true) + .UseStartup() +""" diff --git a/src/Fantomas/CodePrinter.fs b/src/Fantomas/CodePrinter.fs index d850334217..e5cde616ca 100644 --- a/src/Fantomas/CodePrinter.fs +++ b/src/Fantomas/CodePrinter.fs @@ -1789,21 +1789,22 @@ and genExpr astContext synExpr = // This filters a few long examples of App | DotGetAppSpecial (s, es) -> + let genIdent s r ts e = + (!-(sprintf ".%s" s) |> genTriviaFor Ident_ r) + +> genGenericTypeParameters astContext ts + +> ifElse (hasParenthesis e || isArrayOrList e) sepNone sepSpace + let shortExpr = !-s +> atCurrentColumn - (colAutoNlnSkip0 sepNone es (fun ((s, r), e) -> - ((!-(sprintf ".%s" s) |> genTriviaFor Ident_ r) - +> ifElse (hasParenthesis e || isArrayOrList e) sepNone sepSpace - +> genExpr astContext e))) + (colAutoNlnSkip0 sepNone es (fun ((s, r), e, ts) -> (genIdent s r ts e +> genExpr astContext e))) let longExpr = !-s +> indent +> sepNln - +> col sepNln es (fun ((s, r), e) -> - (!-(sprintf ".%s" s) |> genTriviaFor Ident_ r) - +> ifElse (hasParenthesis e || isArrayOrList e) sepNone sepSpace + +> col sepNln es (fun ((s, r), e, ts) -> + genIdent s r ts e +> genExpr { astContext with IsInsideDotGet = true } @@ -1840,9 +1841,7 @@ and genExpr astContext synExpr = +> indent +> sep +> col sep (List.tail lids) (fun s -> !-(sprintf ".%s" s)) - -- "<" - +> col sepComma ts (genType astContext false) - -- ">" + +> genGenericTypeParameters astContext ts +> genExpr astContext e2 +> unindent | App (e1, [ e2 ]) -> @@ -1863,7 +1862,7 @@ and genExpr astContext synExpr = |> fun genTrivia -> genTrivia f match es with - | [ (s, _), e ] when (not (hasParenthesis e)) -> + | [ (s, _), e, [] ] when (not (hasParenthesis e)) -> expr sep +> genTriviaOfIdent (!-(sprintf ".%s" s)) e +> sepSpaceOrIndentAndNlnIfExpressionExceedsPageWidth (genExpr astContext e) @@ -1871,16 +1870,37 @@ and genExpr astContext synExpr = expr sep +> indent +> sep - +> (col sep es (fun ((s, _), e) -> - let currentIdentifier = genTriviaOfIdent (!-(sprintf ".%s" s)) e - let hasParenthesis = hasParenthesis e - - (currentIdentifier - +> ifElse hasParenthesis sepNone sepSpace - +> genExpr - { astContext with - IsInsideDotGet = true } - e))) + +> coli sep es (fun idx ((s, _), e, ts) -> + let currentIdentifier = + genTriviaOfIdent (!-(sprintf ".%s" s)) e + +> genGenericTypeParameters astContext ts + + let hasParenthesis = hasParenthesis e + + let isLastAndTyped = + (List.length es - 1) = idx && List.isNotEmpty ts + + currentIdentifier + +> (fun ctx -> + let isUpper = Char.IsUpper(s.[0]) + + let expr = + if (isLastAndTyped + && isUpper + && ctx.Config.SpaceBeforeUppercaseInvocation) + || (isLastAndTyped + && not isUpper + && ctx.Config.SpaceBeforeLowercaseInvocation) + || (not isLastAndTyped && not hasParenthesis) then + sepSpace + else + sepNone + + expr ctx) + +> genExpr + { astContext with + IsInsideDotGet = true } + e) +> unindent isShortExpression ctx.Config.MaxDotGetExpressionWidth (genExpr sepNone) (genExpr sepNln) ctx @@ -1972,9 +1992,7 @@ and genExpr astContext synExpr = | TypeApp (e, ts) -> genExpr astContext e - -- "<" - +> col sepComma ts (genType astContext false) - -- ">" + +> genGenericTypeParameters astContext ts | LetOrUses (bs, e) -> let isFromAst (ctx: Context) = ctx.Content = String.Empty @@ -2639,6 +2657,14 @@ and genExpr astContext synExpr = | SynExpr.DotGet _ -> genTriviaFor SynExpr_DotGet synExpr.Range | _ -> id) +and genGenericTypeParameters astContext ts = + match ts with + | [] -> sepNone + | ts -> + !- "<" + +> col sepComma ts (genType astContext false) + -- ">" + and genMultilineRecordInstance (inheritOpt: (SynType * SynExpr) option) (xs: (RecordFieldName * SynExpr option * BlockSeparator option) list) (eo: SynExpr option) diff --git a/src/Fantomas/SourceParser.fs b/src/Fantomas/SourceParser.fs index a3d5470dcc..df4a440623 100644 --- a/src/Fantomas/SourceParser.fs +++ b/src/Fantomas/SourceParser.fs @@ -971,8 +971,11 @@ let (|DotGet|_|) = /// Gather series of application for line breaking let rec (|DotGetApp|_|) = function - | SynExpr.App (_, _, DotGet (DotGetApp (e, es), s), e', _) -> Some(e, [ yield! es; yield (s, e') ]) - | SynExpr.App (_, _, DotGet (e, s), e', _) -> Some(e, [ (s, e') ]) + | SynExpr.App (_, _, DotGet (DotGetApp (e, es), s), e', _) -> Some(e, [ yield! es; yield (s, e', []) ]) + | SynExpr.App (_, _, DotGet (e, s), e', _) -> Some(e, [ (s, e', []) ]) + | SynExpr.App (_, _, SynExpr.TypeApp (DotGet (DotGetApp (e, es), s), _, ts, _, _, _, _), e', _) -> + Some(e, [ yield! es; yield (s, e', ts) ]) + | SynExpr.App (_, _, SynExpr.TypeApp (DotGet (e, s), _, ts, _, _, _, _), e', _) -> Some(e, [ (s, e', ts) ]) | _ -> None let (|DotGetAppSpecial|_|) = @@ -981,7 +984,7 @@ let (|DotGetAppSpecial|_|) = let i = s.IndexOf(".") if i <> -1 - then Some((s.[..i - 1]), ((s.[i + 1..], sx.Range), e) :: es) + then Some((s.[..i - 1]), ((s.[i + 1..], sx.Range), e, []) :: es) else None | _ -> None From 3a506d9476e6656c57e375bba861e86e029b4224 Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 18 Sep 2020 18:14:29 +0200 Subject: [PATCH 3/4] Add documentation for fsharp_max_dot_get_expression_width. --- docs/Documentation.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/Documentation.md b/docs/Documentation.md index bc5e750eb1..46ec01b357 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -78,6 +78,7 @@ fsharp_max_record_width=40 fsharp_max_array_or_list_width=40 fsharp_max_value_binding_width=40 fsharp_max_function_binding_width=40 +fsharp_max_dot_get_expression_width=50 fsharp_multiline_block_brackets_on_same_column=false fsharp_newline_between_type_definition_and_members=false fsharp_keep_if_then_in_same_line=false @@ -563,6 +564,28 @@ type Triangle() = width * height / 2 ``` +### fsharp_max_dot_get_expression_width + +Control the maximum width for which (nested) [SynExpr.DotGet](https://fsharp.github.io/FSharp.Compiler.Service/reference/fsharp-compiler-syntaxtree-synexpr.html) expressions should be in one line. +Default = 50. + +`defaultConfig` + +```fsharp +let job = + JobBuilder + .UsingJobData(jobDataMap) + .Create() + .Build() +``` + +`{ defaultConfig with MaxDotGetExpressionWidth = 100 }` + +```fsharp +let job = + JobBuilder.UsingJobData(jobDataMap).Create().Build() +``` + ### fsharp_multiline_block_brackets_on_same_column Alternative way of formatting records, arrays and lists. This will align the braces at the same column level. From 35f8c4203d6f162a2f11feded967fce7369550d1 Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 18 Sep 2020 18:18:35 +0200 Subject: [PATCH 4/4] Bump to 4.2.0-alpha-001. --- RELEASE_NOTES.md | 4 ++++ .../Fantomas.CoreGlobalTool.Tests.fsproj | 2 +- src/Fantomas.CoreGlobalTool/Fantomas.CoreGlobalTool.fsproj | 2 +- src/Fantomas.Extras/Fantomas.Extras.fsproj | 2 +- src/Fantomas.Tests/Fantomas.Tests.fsproj | 2 +- src/Fantomas/Fantomas.fsproj | 2 +- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index bf0fe27fab..09858e1276 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +### 4.2.0-alpha-001 - 09/2020 + +* Feature MaxDotGetExpressionWidth. [#501](https://github.com/fsprojects/fantomas/issues/501) + ### 4.1.1 - 09/2020 * Fix No newline between module and first declaration. [#1139](https://github.com/fsprojects/fantomas/issues/1139) diff --git a/src/Fantomas.CoreGlobalTool.Tests/Fantomas.CoreGlobalTool.Tests.fsproj b/src/Fantomas.CoreGlobalTool.Tests/Fantomas.CoreGlobalTool.Tests.fsproj index cdd1ca300d..4ac4c540e1 100644 --- a/src/Fantomas.CoreGlobalTool.Tests/Fantomas.CoreGlobalTool.Tests.fsproj +++ b/src/Fantomas.CoreGlobalTool.Tests/Fantomas.CoreGlobalTool.Tests.fsproj @@ -4,7 +4,7 @@ netcoreapp3.1 false false - 4.1.1 + 4.2.0-alpha-001 FS0988 FS0025 diff --git a/src/Fantomas.CoreGlobalTool/Fantomas.CoreGlobalTool.fsproj b/src/Fantomas.CoreGlobalTool/Fantomas.CoreGlobalTool.fsproj index 26ed4551ab..6df63697e6 100644 --- a/src/Fantomas.CoreGlobalTool/Fantomas.CoreGlobalTool.fsproj +++ b/src/Fantomas.CoreGlobalTool/Fantomas.CoreGlobalTool.fsproj @@ -4,7 +4,7 @@ netcoreapp3.1 fantomas True - 4.1.1 + 4.2.0-alpha-001 fantomas-tool FS0025 diff --git a/src/Fantomas.Extras/Fantomas.Extras.fsproj b/src/Fantomas.Extras/Fantomas.Extras.fsproj index e2f884a450..eccb2dbc8c 100644 --- a/src/Fantomas.Extras/Fantomas.Extras.fsproj +++ b/src/Fantomas.Extras/Fantomas.Extras.fsproj @@ -2,7 +2,7 @@ netstandard2.0 - 4.1.1 + 4.2.0-alpha-001 Utility package for Fantomas FS0025 diff --git a/src/Fantomas.Tests/Fantomas.Tests.fsproj b/src/Fantomas.Tests/Fantomas.Tests.fsproj index fcbb100514..08b992878e 100644 --- a/src/Fantomas.Tests/Fantomas.Tests.fsproj +++ b/src/Fantomas.Tests/Fantomas.Tests.fsproj @@ -1,6 +1,6 @@ - 4.1.1 + 4.2.0-alpha-001 FS0988 netcoreapp3.1 FS0025 diff --git a/src/Fantomas/Fantomas.fsproj b/src/Fantomas/Fantomas.fsproj index 12b7e35065..680e394be2 100644 --- a/src/Fantomas/Fantomas.fsproj +++ b/src/Fantomas/Fantomas.fsproj @@ -2,7 +2,7 @@ netstandard2.0 - 4.1.1 + 4.2.0-alpha-001 Source code formatter for F# FS0025