From 4349e0226d5efd9eefe10bdd18cc6fb847868e0b Mon Sep 17 00:00:00 2001 From: nojaf Date: Mon, 9 Jan 2023 10:31:23 +0100 Subject: [PATCH 1/7] Initial chain proof of concept. --- src/Fantomas.Core.Tests/ChainTests.fs | 277 +++++++++++ .../CompilerDirectivesTests.fs | 4 +- src/Fantomas.Core.Tests/DotGetTests.fs | 131 ++---- src/Fantomas.Core.Tests/DotIndexedGetTests.fs | 6 +- .../Fantomas.Core.Tests.fsproj | 1 + src/Fantomas.Core.Tests/IfThenElseTests.fs | 8 +- src/Fantomas.Core.Tests/LambdaTests.fs | 10 +- .../MultiLineLambdaClosingNewlineTests.fs | 42 +- src/Fantomas.Core.Tests/RecordTests.fs | 12 +- .../SpaceBeforeUppercaseInvocationTests.fs | 42 +- .../SpecialConstructsTests.fs | 3 +- .../TypeDeclarationTests.fs | 3 +- src/Fantomas.Core/ASTTransformer.fs | 373 ++++++++++----- src/Fantomas.Core/CodePrinter.fs | 429 ++++++------------ src/Fantomas.Core/Selection.fs | 19 +- src/Fantomas.Core/SyntaxOak.fs | 131 ++---- 16 files changed, 834 insertions(+), 657 deletions(-) create mode 100644 src/Fantomas.Core.Tests/ChainTests.fs diff --git a/src/Fantomas.Core.Tests/ChainTests.fs b/src/Fantomas.Core.Tests/ChainTests.fs new file mode 100644 index 0000000000..6fe2a92ed5 --- /dev/null +++ b/src/Fantomas.Core.Tests/ChainTests.fs @@ -0,0 +1,277 @@ +module Fantomas.Core.Tests.ChainTests + +open NUnit.Framework +open FsUnit +open Fantomas.Core.Tests.TestHelper + +[] +let ``appUnit dot identifier`` () = + formatSourceString + false + """ +X().Y +""" + config + |> prepend newline + |> should + equal + """ +X().Y +""" + +[] +let ``appParen dot identifier`` () = + formatSourceString + false + """ +X(a).Y +""" + config + |> prepend newline + |> should + equal + """ +X(a).Y +""" + +[] +let ``appUnit dot appUnit`` () = + formatSourceString + false + """ +X().Y() +""" + config + |> prepend newline + |> should + equal + """ +X().Y() +""" + +[] +let ``typed appUnit dot identifier`` () = + formatSourceString + false + """ +X().Y +X().Y() +""" + config + |> prepend newline + |> should + equal + """ +X().Y +X().Y() +""" + +[] +let ``appParenLambda dot identifier`` () = + formatSourceString + false + """ +X(fun x -> x).Y +""" + config + |> prepend newline + |> should + equal + """ +X(fun x -> x).Y +""" + +[] +let ``identifier dot appUnit dot identifier`` () = + formatSourceString + false + """ +X.Y().Z +""" + config + |> prepend newline + |> should + equal + """ +X.Y().Z +""" + +[] +let ``identifier dot indexed expr dot identifier`` () = + formatSourceString + false + """ +A.[0].B +""" + config + |> prepend newline + |> should + equal + """ +A.[0].B +""" + +[] +let ``identifier dot indexed expr dot appParenExpr`` () = + formatSourceString + false + """ +A.[0].B(1) +""" + config + |> prepend newline + |> should + equal + """ +A.[0].B(1) +""" + +[] +let ``identifier dot typed appUnit dot identifier`` () = + formatSourceString + false + """ +X.Y().Z +""" + config + |> prepend newline + |> should + equal + """ +X.Y().Z +""" + +[] +let ``identifier dot typed identifier dot identifier`` () = + formatSourceString + false + """ +X.Y.Z +""" + config + |> prepend newline + |> should + equal + """ +X.Y.Z +""" + +[] +let ``appUnit dot appParen`` () = + formatSourceString + false + """ +A().B(fun b -> b) +""" + config + |> prepend newline + |> should + equal + """ +A().B(fun b -> b) +""" + +[] +let ``2685 `` () = + formatSourceString + false + """ +module A = + let foo = + Foai.SomeLongTextYikes().ConfigureBarry(fun alpha beta gamma -> + context.AddSomething ("a string") |> ignore + ).MoreContext(fun builder -> + // also good stuff + () + ).ABC().XYZ +""" + { config with + SpaceBeforeUppercaseInvocation = true } + |> prepend newline + |> should + equal + """ +module A = + let foo = + Foai + .SomeLongTextYikes() + .ConfigureBarry(fun alpha beta gamma -> context.AddSomething ("a string") |> ignore) + .MoreContext(fun builder -> + // also good stuff + ()) + .ABC() + .XYZ +""" + +[] +let ``identifier dot appUnit dot typed appUnit `` () = + formatSourceString + false + """ +A.B().C<'d>() +""" + { config with + MaxDotGetExpressionWidth = 0 } + |> prepend newline + |> should + equal + """ +A + .B() + .C<'d>() +""" + +[] +let ``identifier dot appUnit dot typed identifier `` () = + formatSourceString + false + """ +A.B().C<'d> +""" + { config with + MaxDotGetExpressionWidth = 0 } + |> prepend newline + |> should + equal + """ +A + .B() + .C<'d> +""" + +[] +let ``identifier dot identifier dot appExpr dot appUnit dot index expr`` () = + formatSourceString + false + """ +A.B.C(D).E().[0] +""" + { config with + MaxDotGetExpressionWidth = 0 } + |> prepend newline + |> should + equal + """ +A.B + .C(D) + .E() + .[0] +""" + +[] +let ``identifier dot identifier dot appExpr dot identifier dot index expr`` () = + formatSourceString + false + """ +A.B.C(D).E.[0] +""" + { config with + MaxDotGetExpressionWidth = 0 } + |> prepend newline + |> should + equal + """ +A.B + .C(D) + .E.[0] +""" diff --git a/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs b/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs index bd7a5dd3d6..51f75cee9a 100644 --- a/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs +++ b/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs @@ -2287,9 +2287,7 @@ let loader (projectRoot: string) (siteContent: SiteContents) = """ let loadFile n = let file = - System - .IO - .Path + System.IO.Path .Combine( contentDir, (n |> System.IO.Path.GetFileNameWithoutExtension) diff --git a/src/Fantomas.Core.Tests/DotGetTests.fs b/src/Fantomas.Core.Tests/DotGetTests.fs index 8cd6280e31..8eafc1a035 100644 --- a/src/Fantomas.Core.Tests/DotGetTests.fs +++ b/src/Fantomas.Core.Tests/DotGetTests.fs @@ -18,10 +18,7 @@ Microsoft.FSharp.Reflection.FSharpType.GetUnionCases(typeof> |> should equal """ -Microsoft - .FSharp - .Reflection - .FSharpType +Microsoft.FSharp.Reflection.FSharpType .GetUnionCases( typeof>> .GetGenericTypeDefinition() @@ -45,13 +42,9 @@ System.Diagnostics.FileVersionInfo.GetVersionInfo( |> should equal """ -System - .Diagnostics - .FileVersionInfo +System.Diagnostics.FileVersionInfo .GetVersionInfo( - System - .Reflection - .Assembly + System.Reflection.Assembly .GetExecutingAssembly() .Location ) @@ -79,13 +72,9 @@ let ``split chained method call expression, 246`` () = root.SetAttribute( "driverVersion", "AltCover.Recorder " - + System - .Diagnostics - .FileVersionInfo + + System.Diagnostics.FileVersionInfo .GetVersionInfo( - System - .Reflection - .Assembly + System.Reflection.Assembly .GetExecutingAssembly() .Location ) @@ -105,16 +94,8 @@ Equinox.EventStore.Resolver<'event, 'state, _>(gateway, codec, fold, initial, ca |> should equal """ -Equinox - .EventStore - .Resolver<'event, 'state, _>( - gateway, - codec, - fold, - initial, - cacheStrategy, - accessStrategy - ) +Equinox.EventStore + .Resolver<'event, 'state, _>(gateway, codec, fold, initial, cacheStrategy, accessStrategy) .Resolve """ @@ -163,14 +144,8 @@ module Services = ) = match storage with | Storage.MemoryStore store -> - Equinox - .MemoryStore - .Resolver( - store, - FsCodec.Box.Codec.Create(), - fold, - initial - ) + Equinox.MemoryStore + .Resolver(store, FsCodec.Box.Codec.Create(), fold, initial) .Resolve | Storage.EventStore(gateway, cache) -> let accessStrategy = Equinox.EventStore.AccessStrategy.RollingSnapshots snapshot @@ -178,16 +153,8 @@ module Services = let cacheStrategy = Equinox.EventStore.CachingStrategy.SlidingWindow(cache, TimeSpan.FromMinutes 20.) - Equinox - .EventStore - .Resolver<'event, 'state, _>( - gateway, - codec, - fold, - initial, - cacheStrategy, - accessStrategy - ) + Equinox.EventStore + .Resolver<'event, 'state, _>(gateway, codec, fold, initial, cacheStrategy, accessStrategy) .Resolve """ @@ -312,8 +279,7 @@ let firstName = equal """ let firstName = - define - .Attribute + define.Attribute .ParsedRes(FirstName.value, FirstName.create) .Get(fun u -> u.FirstName) .SetRes(userSetter User.setFirstName) @@ -332,14 +298,8 @@ Equinox.MemoryStore.Resolver(store, FsCodec.Box.Codec.Create(), fold, initial) |> should equal """ -Equinox - .MemoryStore - .Resolver( - store, - FsCodec.Box.Codec.Create(), - fold, - initial - ) +Equinox.MemoryStore + .Resolver(store, FsCodec.Box.Codec.Create(), fold, initial) .Resolve """ @@ -361,16 +321,8 @@ let ``long ident with dots inside type app inside dotget`` () = |> should equal """ -Equinox - .EventStore - .Resolver<'event, 'state, _>( - gateway, - codec, - fold, - initial, - cacheStrategy, - accessStrategy - ) +Equinox.EventStore + .Resolver<'event, 'state, _>(gateway, codec, fold, initial, cacheStrategy, accessStrategy) .Resolve """ @@ -393,8 +345,7 @@ let getColl = equal """ let getColl = - define - .Operation + define.Operation .ForContext(Context.toAuthenticatedContext) .GetCollection(fun _ parser -> let x = 2 @@ -678,17 +629,13 @@ type IWebHostBuilderExtensions() = [] static member UseSerilog(webHostBuilder: IWebHostBuilder, index: Index) = webHostBuilder.UseSerilog (fun context configuration -> - configuration - .MinimumLevel + configuration.MinimumLevel .Debug() - .WriteTo - .Logger (fun loggerConfiguration -> - loggerConfiguration - .Enrich + .WriteTo.Logger (fun loggerConfiguration -> + loggerConfiguration.Enrich .WithProperty("host", Environment.MachineName) .Enrich.WithProperty("user", Environment.UserName) - .Enrich - .WithProperty ( + .Enrich.WithProperty ( "application", context.HostingEnvironment.ApplicationName ) @@ -999,8 +946,7 @@ type Foobar = FileSystem.SafeExists filename && ((tcConfig.GetTargetFrameworkDirectories() |> List.exists (fun clrRoot -> clrRoot = Path.GetDirectoryName filename)) - || (tcConfig - .FxResolver + || (tcConfig.FxResolver .GetSystemAssemblies() .Contains(fileNameWithoutExtension filename)) || tcConfig.FxResolver.IsInReferenceAssemblyPackDirectory filename) @@ -1078,8 +1024,7 @@ module Foo = module Foo = let bar () = let saveDir = - fs - .DirectoryInfo + fs.DirectoryInfo .FromDirectoryName( fs.Path.Combine ((ThingThing.rootRoot fs thingThing).FullName, "tada!") ) @@ -1118,8 +1063,7 @@ module Foo = module Foo = let bar () = let saveDir = - fs - .DirectoryInfo + fs.DirectoryInfo .FromDirectoryName( fs.Path.Combine ( (ThingThing.rootRoot fs thingThing).FullName, @@ -1236,7 +1180,8 @@ let ``dotget function application should add space before argument, long`` () = """ m.Property(fun p -> p.Name).IsRequired().HasColumnName("ModelName").HasMaxLength 64 """ - config + { config with + MaxDotGetExpressionWidth = 70 } |> prepend newline |> should equal @@ -1245,7 +1190,8 @@ m .Property(fun p -> p.Name) .IsRequired() .HasColumnName("ModelName") - .HasMaxLength 64 + .HasMaxLength + 64 """ [] @@ -1264,9 +1210,7 @@ m.Property(fun p -> p.Name).IsRequired().HasColumnName("ModelName").HasMaxLength m .Property(fun p -> p.Name) .IsRequired() - .HasColumnName( - "ModelName" - ) + .HasColumnName("ModelName") .HasMaxLength """ @@ -1289,10 +1233,7 @@ db.Schema.Users.Query |> should equal """ -db - .Schema - .Users - .Query +db.Schema.Users.Query .Where(fun x -> x.Role) .Matches( function @@ -1300,10 +1241,7 @@ db | _ -> __ ) .In( - db - .Schema - .Companies - .Query + db.Schema.Companies.Query .Where(fun x -> x.LicenceId) .Equals(licenceId) .Select(fun x -> x.Id) @@ -1356,11 +1294,8 @@ module Program = let builder = WebAssemblyHostBuilder.CreateDefault(args) builder.RootComponents.Add("#main") - builder - .Services - .AddRemoting( - builder.HostEnvironment - ) + builder.Services + .AddRemoting(builder.HostEnvironment) .Services #if (!DEBUG) .RemoveAll() diff --git a/src/Fantomas.Core.Tests/DotIndexedGetTests.fs b/src/Fantomas.Core.Tests/DotIndexedGetTests.fs index e2afb14875..d4a61345b6 100644 --- a/src/Fantomas.Core.Tests/DotIndexedGetTests.fs +++ b/src/Fantomas.Core.Tests/DotIndexedGetTests.fs @@ -120,14 +120,14 @@ a.Some.Thing( |> should equal """ -a - .Some +a.Some .Thing( "aaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ) - .Meh().[0] + .Meh() + .[0] """ [] diff --git a/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj b/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj index 39d36c60ff..77034f3b80 100644 --- a/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj +++ b/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj @@ -119,6 +119,7 @@ + diff --git a/src/Fantomas.Core.Tests/IfThenElseTests.fs b/src/Fantomas.Core.Tests/IfThenElseTests.fs index 30bef75242..2e356f7fbc 100644 --- a/src/Fantomas.Core.Tests/IfThenElseTests.fs +++ b/src/Fantomas.Core.Tests/IfThenElseTests.fs @@ -2457,12 +2457,8 @@ module Foo = module Foo = let bar = if - Regex( - "long long long long long long long long long" - ) - .Match( - s - ) + Regex("long long long long long long long long long") + .Match(s) .Success then None diff --git a/src/Fantomas.Core.Tests/LambdaTests.fs b/src/Fantomas.Core.Tests/LambdaTests.fs index d72ef566c1..3b1c65d3db 100644 --- a/src/Fantomas.Core.Tests/LambdaTests.fs +++ b/src/Fantomas.Core.Tests/LambdaTests.fs @@ -319,7 +319,8 @@ CloudStorageAccount.SetConfigurationSettingPublisher(fun configName configSettin if hostedService then RoleEnvironment.GetConfigurationSettingValue(configName) else - ConfigurationManager.ConnectionStrings.[configName] + ConfigurationManager + .ConnectionStrings.[configName] .ConnectionString configSettingPublisher.Invoke(connectionString) @@ -1293,9 +1294,10 @@ type Item() = let items = [ Item(); Item(); Item() ] let firstOrDef = - items.FirstOrDefault(fun x -> - x.ValidFrom <= DateTime.Now - || x.ValidFrom > DateTime.Now) + items + .FirstOrDefault(fun x -> + x.ValidFrom <= DateTime.Now + || x.ValidFrom > DateTime.Now) .Value """ diff --git a/src/Fantomas.Core.Tests/MultiLineLambdaClosingNewlineTests.fs b/src/Fantomas.Core.Tests/MultiLineLambdaClosingNewlineTests.fs index d0b2766a05..99d23671d1 100644 --- a/src/Fantomas.Core.Tests/MultiLineLambdaClosingNewlineTests.fs +++ b/src/Fantomas.Core.Tests/MultiLineLambdaClosingNewlineTests.fs @@ -975,13 +975,10 @@ configuration |> should equal """ -configuration - .MinimumLevel +configuration.MinimumLevel .Debug() - .WriteTo - .Logger(fun loggerConfiguration -> - loggerConfiguration - .Enrich + .WriteTo.Logger(fun loggerConfiguration -> + loggerConfiguration.Enrich .WithProperty("host", Environment.MachineName) .Enrich.WithProperty("user", Environment.UserName) .Enrich.WithProperty("application", context.HostingEnvironment.ApplicationName) @@ -1006,8 +1003,7 @@ configuration |> should equal """ -configuration - .MinimumLevel +configuration.MinimumLevel .Debug() .WriteTo.Logger(fun x -> x * x) """ @@ -1057,23 +1053,21 @@ configuration |> should equal """ -configuration - .MinimumLevel +configuration.MinimumLevel .Debug() - .WriteTo - .Logger(fun - (a0: int) - (a1: int) - (a2: int) - (a3: int) - (a4: int) - (a5: int) - (a6: int) - (a7: int) - (a8: int) - (a9: int) - (a10: int) - (a11: int) -> + .WriteTo.Logger(fun + (a0: int) + (a1: int) + (a2: int) + (a3: int) + (a4: int) + (a5: int) + (a6: int) + (a7: int) + (a8: int) + (a9: int) + (a10: int) + (a11: int) -> // () ) diff --git a/src/Fantomas.Core.Tests/RecordTests.fs b/src/Fantomas.Core.Tests/RecordTests.fs index 026f42bd15..675fcbf368 100644 --- a/src/Fantomas.Core.Tests/RecordTests.fs +++ b/src/Fantomas.Core.Tests/RecordTests.fs @@ -442,10 +442,11 @@ let ``meaningful space should be preserved, 353`` () = |> should equal """ -to'.WithCommon(fun o' -> - { dotnetOptions o' with - WorkingDirectory = Path.getFullName "RegressionTesting/issue29" - Verbosity = Some DotNet.Verbosity.Minimal }) +to' + .WithCommon(fun o' -> + { dotnetOptions o' with + WorkingDirectory = Path.getFullName "RegressionTesting/issue29" + Verbosity = Some DotNet.Verbosity.Minimal }) .WithParameters """ @@ -543,8 +544,7 @@ I wanted to know why you created Fable. Did you always plan to use F#? Or were y type Database = static member Default() = - Database - .Lowdb + Database.Lowdb .defaults( { Version = CurrentVersion Questions = diff --git a/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs b/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs index 2fdf2030db..0d2437eeef 100644 --- a/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs +++ b/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs @@ -62,15 +62,7 @@ let x = DateTimeOffset(2017,6,1,10,3,14,TimeSpan(1,30,0)).LocalDateTime equal """ let x = - DateTimeOffset( - 2017, - 6, - 1, - 10, - 3, - 14, - TimeSpan (1, 30, 0) - ) + DateTimeOffset(2017, 6, 1, 10, 3, 14, TimeSpan (1, 30, 0)) .LocalDateTime """ @@ -318,3 +310,35 @@ match x with | D (e = f) -> () | g.H (i = j) -> () """ + +[] +let ``never add a space before paren lambda in chain, 2685`` () = + formatSourceString + false + """ +module A = + let foo = + Foai.SomeLongTextYikes().ConfigureBarry(fun alpha beta gamma -> + context.AddSomething ("a string") |> ignore + ).MoreContext(fun builder -> + // also good stuff + () + ).ABC().XYZ + +""" + spaceBeforeConfig + |> prepend newline + |> should + equal + """ +module A = + let foo = + Foai + .SomeLongTextYikes() + .ConfigureBarry(fun alpha beta gamma -> context.AddSomething ("a string") |> ignore) + .MoreContext(fun builder -> + // also good stuff + ()) + .ABC() + .XYZ +""" diff --git a/src/Fantomas.Core.Tests/SpecialConstructsTests.fs b/src/Fantomas.Core.Tests/SpecialConstructsTests.fs index 7782a51832..f6b3461257 100644 --- a/src/Fantomas.Core.Tests/SpecialConstructsTests.fs +++ b/src/Fantomas.Core.Tests/SpecialConstructsTests.fs @@ -22,7 +22,8 @@ let inline private retype<'T, 'U> (x: 'T) : 'U = (# "" x : 'U #) let ``don't add whitespace in chained accessors, 566`` () = formatSourceString false - """type F = + """ +type F = abstract G : int list -> Map let x : F = { new F with member __.G _ = Map.empty } diff --git a/src/Fantomas.Core.Tests/TypeDeclarationTests.fs b/src/Fantomas.Core.Tests/TypeDeclarationTests.fs index 43ed43f9ca..a706e42d92 100644 --- a/src/Fantomas.Core.Tests/TypeDeclarationTests.fs +++ b/src/Fantomas.Core.Tests/TypeDeclarationTests.fs @@ -824,7 +824,8 @@ type BlobHelper(Account: CloudStorageAccount) = if hostedService then RoleEnvironment.GetConfigurationSettingValue(configName) else - ConfigurationManager.ConnectionStrings.[configName] + ConfigurationManager + .ConnectionStrings.[configName] .ConnectionString configSettingPublisher.Invoke(connectionString) diff --git a/src/Fantomas.Core/ASTTransformer.fs b/src/Fantomas.Core/ASTTransformer.fs index 36d4207283..6e31b77fec 100644 --- a/src/Fantomas.Core/ASTTransformer.fs +++ b/src/Fantomas.Core/ASTTransformer.fs @@ -112,9 +112,7 @@ let mkConstant (creationAide: CreationAide) c r : Constant = stn (creationAide.TextFromSource (fun () -> fallback) r) r |> Constant.FromText match c with - | SynConst.Unit -> - match r with - | StartEndRange 1 (lpr, _, rpr) -> UnitNode(stn "(" lpr, stn ")" rpr, r) |> Constant.Unit + | SynConst.Unit -> mkUnit r |> Constant.Unit | SynConst.Bool b -> stn (if b then "true" else "false") r |> Constant.FromText | SynConst.Byte v -> orElse $"%A{v}" | SynConst.SByte v -> orElse $"%A{v}" @@ -182,7 +180,7 @@ let mkMeasure (creationAide: CreationAide) (measure: SynMeasure) : Measure = let mkAttribute (creationAide: CreationAide) (a: SynAttribute) = let expr = match a.ArgExpr with - | SynExpr.Const(SynConst.Unit, _) -> None + | UnitExpr _ -> None | e -> mkExpr creationAide e |> Some AttributeNode(mkSynLongIdent a.TypeName, expr, Option.map mkIdent a.Target, a.Range) @@ -600,31 +598,258 @@ let (|App|_|) e = let head, xs = visit e id if xs.Count = 0 then None else Some(head, Seq.toList xs) -let (|DotGetAppParenExpr|_|) e = - match e with - | SynExpr.Paren(expr = SynExpr.Lambda _) - | SynExpr.Paren(expr = SynExpr.MatchLambda _) -> None - | SynExpr.Paren _ - | SynExpr.Const(constant = SynConst.Unit _) -> Some e - | _ -> None - let (|ParenLambda|_|) e = match e with - | SynExpr.Paren(SynExpr.Lambda(_, _, _, _, Some(pats, body), mLambda, { ArrowRange = Some mArrow }), - lpr, - Some rpr, - _) -> Some(lpr, pats, mArrow, body, mLambda, rpr) + | ParenExpr(lpr, SynExpr.Lambda(_, _, _, _, Some(pats, body), mLambda, { ArrowRange = Some mArrow }), rpr, _) -> + Some(lpr, pats, mArrow, body, mLambda, rpr) | _ -> None let (|ParenMatchLambda|_|) e = match e with - | SynExpr.Paren(SynExpr.MatchLambda(_, mFunction, clauses, _, mMatchLambda), lpr, Some rpr, _) -> + | ParenExpr(lpr, SynExpr.MatchLambda(_, mFunction, clauses, _, mMatchLambda), rpr, _) -> Some(lpr, mFunction, clauses, mMatchLambda, rpr) | _ -> None let mkMatchLambda creationAide mFunction cs m = ExprMatchLambdaNode(stn "function" mFunction, List.map (mkSynMatchClause creationAide) cs, m) +[] +type LinkExpr = + | Identifier of + // Could be SynExpr.LongIdent or SynExpr.TypeApp(LongIdent) + SynExpr + | Dot of range + | Expr of SynExpr + | AppParenLambda of functionName: SynExpr * parenLambda: SynExpr + | AppParen of functionName: SynExpr * lpr: range * e: SynExpr * rpr: range * pr: range + | AppUnit of functionName: SynExpr * unit: range + | IndexExpr of indexExpr: SynExpr + +let mkLinksFromSynLongIdent (sli: SynLongIdent) : LinkExpr list = + let idents = + List.map (mkLongIdentExprFromSynIdent >> LinkExpr.Identifier) sli.IdentsWithTrivia + + let dots = List.map LinkExpr.Dot sli.Dots + + [ yield! idents; yield! dots ] + |> List.sortBy (function + | LinkExpr.Identifier identifierExpr -> identifierExpr.Range.StartLine, identifierExpr.Range.StartColumn + | LinkExpr.Dot m -> m.StartLine, m.StartColumn + | LinkExpr.Expr _ + | LinkExpr.AppParenLambda _ + | LinkExpr.AppParen _ + | LinkExpr.AppUnit _ + | LinkExpr.IndexExpr _ -> -1, -1) + +let (|UnitExpr|_|) e = + match e with + | SynExpr.Const(constant = SynConst.Unit _) -> Some e.Range + | _ -> None + +let (|ParenExpr|_|) e = + match e with + | SynExpr.Paren(e, lpr, Some rpr, pr) -> Some(lpr, e, rpr, pr) + | _ -> None + +let mkLongIdentExprFromSynIdent (SynIdent(ident, identTrivia)) = + SynExpr.LongIdent(false, SynLongIdent([ ident ], [], [ identTrivia ]), None, ident.idRange) + +let mkLinksFromFunctionName (mkLinkFromExpr: SynExpr -> LinkExpr) (functionName: SynExpr) : LinkExpr list = + match functionName with + | SynExpr.TypeApp(SynExpr.LongIdent(longDotId = sli), + lessRange, + typeArgs, + commaRanges, + Some greaterRange, + typeArgsRange, + _) -> + match sli.IdentsWithTrivia with + | [] + | [ _ ] -> [ mkLinkFromExpr functionName ] + | synIdents -> + let leftLinks = mkLinksFromSynLongIdent sli + let lastSynIdent = List.last synIdents + + let m = + let (SynIdent(ident, _)) = lastSynIdent + unionRanges ident.idRange greaterRange + + let typeAppExpr = + SynExpr.TypeApp( + mkLongIdentExprFromSynIdent lastSynIdent, + lessRange, + typeArgs, + commaRanges, + Some greaterRange, + typeArgsRange, + m + ) + + [ yield! List.take (leftLinks.Length - 1) leftLinks + yield mkLinkFromExpr typeAppExpr ] + + | SynExpr.LongIdent(longDotId = sli) -> + match sli.IdentsWithTrivia with + | [] + | [ _ ] -> [ mkLinkFromExpr functionName ] + | synIdents -> + let leftLinks = mkLinksFromSynLongIdent sli + let lastSynIdent = List.last synIdents + + [ yield! List.take (leftLinks.Length - 1) leftLinks + yield (mkLongIdentExprFromSynIdent lastSynIdent |> mkLinkFromExpr) ] + | e -> [ mkLinkFromExpr e ] + +let (|ChainExpr|_|) (e: SynExpr) : LinkExpr list option = + let rec visit (e: SynExpr) (continuation: LinkExpr list -> LinkExpr list) = + match e with + | SynExpr.App( + isInfix = false + funcExpr = SynExpr.TypeApp(SynExpr.DotGet _ as funcExpr, + lessRange, + typeArgs, + commaRanges, + Some greaterRange, + typeArgsRange, + _) + argExpr = ParenExpr _ | UnitExpr _ as argExpr) -> + visit funcExpr (fun leftLinks -> + let lastLink = + match List.tryLast leftLinks with + | Some(LinkExpr.Identifier identifierExpr) -> + let typeApp = + SynExpr.TypeApp( + identifierExpr, + lessRange, + typeArgs, + commaRanges, + Some greaterRange, + typeArgsRange, + unionRanges identifierExpr.Range greaterRange + ) + + match argExpr with + | UnitExpr mUnit -> [ LinkExpr.AppUnit(typeApp, mUnit) ] + | ParenExpr(lpr, innerExpr, rpr, pr) -> [ LinkExpr.AppParen(typeApp, lpr, innerExpr, rpr, pr) ] + | _ -> [] + | _ -> [] + + let leftLinks = List.take (leftLinks.Length - 1) leftLinks + + continuation [ yield! leftLinks; yield! lastLink ]) + + | SynExpr.TypeApp(SynExpr.DotGet _ as dotGet, + lessRange, + typeArgs, + commaRanges, + Some greaterRange, + typeArgsRange, + _) -> + visit dotGet (fun leftLinks -> + let lastLink = + match List.tryLast leftLinks with + | Some(LinkExpr.Identifier property) -> + [ SynExpr.TypeApp( + property, + lessRange, + typeArgs, + commaRanges, + Some greaterRange, + typeArgsRange, + unionRanges property.Range greaterRange + ) + |> LinkExpr.Identifier ] + | _ -> [] + + let leftLinks = List.take (leftLinks.Length - 1) leftLinks + continuation [ yield! leftLinks; yield! lastLink ]) + | SynExpr.App(isInfix = false; funcExpr = SynExpr.DotGet _ as funcExpr; argExpr = argExpr) -> + visit funcExpr (fun leftLinks -> + match List.tryLast leftLinks with + | Some(LinkExpr.Identifier(identifierExpr)) -> + match argExpr with + | UnitExpr mUnit -> + let leftLinks = List.take (leftLinks.Length - 1) leftLinks + + // Compose a function application by taking the last identifier of the SynExpr.DotGet + // and the following argument expression. + // Example: X().Y() -> Take `Y` as function name and `()` as argument. + let rightLink = LinkExpr.AppUnit(identifierExpr, mUnit) + + continuation [ yield! leftLinks; yield rightLink ] + + | ParenExpr(lpr, e, rpr, pr) -> + let leftLinks = List.take (leftLinks.Length - 1) leftLinks + // Example: A().B(fun b -> b) + let rightLink = LinkExpr.AppParen(identifierExpr, lpr, e, rpr, pr) + continuation [ yield! leftLinks; yield rightLink ] + + | _ -> visit argExpr (fun rightLinks -> continuation [ yield! leftLinks; yield! rightLinks ]) + | _ -> visit argExpr (fun rightLinks -> continuation [ yield! leftLinks; yield! rightLinks ])) + + | SynExpr.DotGet(expr, rangeOfDot, longDotId, _) -> + visit expr (fun links -> + continuation + [ yield! links + yield LinkExpr.Dot rangeOfDot + yield! mkLinksFromSynLongIdent longDotId ]) + + | SynExpr.App(isInfix = false; funcExpr = funcExpr; argExpr = UnitExpr mUnit) -> + mkLinksFromFunctionName (fun e -> LinkExpr.AppUnit(e, mUnit)) funcExpr + |> continuation + + | SynExpr.App(isInfix = false; funcExpr = funcExpr; argExpr = ParenExpr(lpr, e, rpr, pr)) -> + mkLinksFromFunctionName (fun f -> LinkExpr.AppParen(f, lpr, e, rpr, pr)) funcExpr + |> continuation + + | SynExpr.App(ExprAtomicFlag.Atomic, + false, + (SynExpr.LongIdent _ as funcExpr), + (SynExpr.ArrayOrList _ as argExpr), + _) -> + visit funcExpr (fun leftLinks -> + let app = + match List.tryLast leftLinks with + | Some(LinkExpr.Identifier identifier) -> + [ SynExpr.App( + ExprAtomicFlag.Atomic, + false, + identifier, + argExpr, + unionRanges identifier.Range argExpr.Range + ) + |> LinkExpr.Expr ] + | _ -> [] + + let leftLinks = List.take (leftLinks.Length - 1) leftLinks + continuation [ yield! leftLinks; yield! app ]) + + | SynExpr.TypeApp _ as typeApp -> mkLinksFromFunctionName LinkExpr.Expr typeApp |> continuation + + | SynExpr.LongIdent(longDotId = sli) -> continuation (mkLinksFromSynLongIdent sli) + + | SynExpr.Ident _ -> continuation [ LinkExpr.Identifier e ] + + | SynExpr.DotIndexedGet(objectExpr, indexArgs, dotRange, _) -> + visit objectExpr (fun leftLinks -> + continuation + [ yield! leftLinks + yield LinkExpr.Dot dotRange + yield LinkExpr.IndexExpr indexArgs ]) + + | other -> continuation [ LinkExpr.Expr other ] + + match e with + | SynExpr.App( + isInfix = false + funcExpr = SynExpr.DotGet _ | SynExpr.TypeApp(expr = SynExpr.DotGet _) + argExpr = UnitExpr _ | ParenExpr _) + | SynExpr.DotGet _ + | SynExpr.TypeApp(expr = SynExpr.DotGet _) + | SynExpr.DotIndexedGet(objectExpr = SynExpr.App(funcExpr = SynExpr.DotGet _) | SynExpr.DotGet _) -> + Some(visit e id) + | _ -> None + let (|AppSingleParenArg|_|) = function | App(SynExpr.DotGet _, [ (SynExpr.Paren(expr = SynExpr.Tuple _)) ]) -> None @@ -635,19 +860,8 @@ let (|AppSingleParenArg|_|) = | _ -> Some(e, px) | _ -> None -let rec (|DotGetApp|_|) = - function - | SynExpr.App(_, _, SynExpr.DotGet(expr = DotGetApp(e, es); longDotId = s), e', _) -> - Some(e, [ yield! es; yield (s, None, e') ]) - | SynExpr.App(_, _, SynExpr.DotGet(expr = e; longDotId = s), e', _) -> Some(e, [ s, None, e' ]) - | SynExpr.App(_, - _, - SynExpr.TypeApp(SynExpr.DotGet(expr = DotGetApp(e, es); longDotId = s), lt, ts, _, Some gt, _, _range), - e', - _) -> Some(e, [ yield! es; yield (s, Some(lt, ts, gt), e') ]) - | SynExpr.App(_, _, SynExpr.TypeApp(SynExpr.DotGet(expr = e; longDotId = s), lt, ts, _, Some gt, _, _range), e', _) -> - Some(e, [ s, Some(lt, ts, gt), e' ]) - | _ -> None +let mkParenExpr creationAide lpr e rpr m = + ExprParenNode(stn "(" lpr, mkExpr creationAide e, stn ")" rpr, m) let mkExpr (creationAide: CreationAide) (e: SynExpr) : Expr = let exprRange = e.Range @@ -901,10 +1115,7 @@ let mkExpr (creationAide: CreationAide) (e: SynExpr) : Expr = | SynExpr.LongIdent(longDotId = SynLongIdent([ ident ], [], [ Some(ParenStarSynIdent(lpr, originalNotation, rpr)) ])) -> ExprParenFunctionNameWithStarNode(stn "(" lpr, stn originalNotation ident.idRange, stn ")" rpr, exprRange) |> Expr.ParenFunctionNameWithStar - | SynExpr.Paren(e, lpr, Some rpr, _) -> - ExprParenNode(stn "(" lpr, mkExpr creationAide e, stn ")" rpr, exprRange) - |> Expr.Paren - + | ParenExpr(lpr, e, rpr, pr) -> mkParenExpr creationAide lpr e rpr pr |> Expr.Paren | SynExpr.Dynamic(funcExpr, _, argExpr, _) -> ExprDynamicNode(mkExpr creationAide funcExpr, mkExpr creationAide argExpr, exprRange) |> Expr.Dynamic @@ -946,73 +1157,27 @@ let mkExpr (creationAide: CreationAide) (e: SynExpr) : Expr = ExprIndexWithoutDotNode(mkExpr creationAide identifierExpr, mkExpr creationAide indexExpr, exprRange) |> Expr.IndexWithoutDot - | App(SynExpr.DotGet( - expr = SynExpr.TypeApp(identifier, lessRange, ts, _, Some greaterRange, _, mTypeApp); longDotId = property), - args) -> - let typeAppNode = - ExprTypeAppNode( - mkExpr creationAide identifier, - stn "<" lessRange, - List.map (mkType creationAide) ts, - stn ">" greaterRange, - mTypeApp - ) - - ExprAppDotGetTypeAppNode(typeAppNode, mkSynLongIdent property, List.map (mkExpr creationAide) args, exprRange) - |> Expr.AppDotGetTypeApp - - | SynExpr.DotGet( - expr = App(SynExpr.DotGet( - expr = SynExpr.App(funcExpr = e; argExpr = SynExpr.Paren(expr = SynExpr.Lambda _) as px) - longDotId = appLids), - es) - longDotId = property) -> - ExprDotGetAppDotGetAppParenLambdaNode( - mkExpr creationAide e, - mkExpr creationAide px, - mkSynLongIdent appLids, - List.map (mkExpr creationAide) es, - mkSynLongIdent property, - exprRange - ) - |> Expr.DotGetAppDotGetAppParenLambda - - | SynExpr.DotGet(expr = SynExpr.App(funcExpr = e; argExpr = DotGetAppParenExpr px); longDotId = lids) -> - ExprDotGetAppParenNode(mkExpr creationAide e, mkExpr creationAide px, mkSynLongIdent lids, exprRange) - |> Expr.DotGetAppParen - | DotGetApp(SynExpr.App(funcExpr = e; argExpr = ParenLambda(lpr, pats, mArrow, body, mLambda, rpr)), es) -> - let lambdaNode = mkLambda creationAide pats mArrow body mLambda - - let parenLambdaNode = - ExprParenLambdaNode(stn "(" lpr, lambdaNode, stn ")" rpr, exprRange) - - let args = - es - |> List.map (fun (s, t, e) -> - let m = unionRanges s.Range e.Range - - let tpi = - t - |> Option.map (fun (lt, ts, gt) -> stn "<" lt, List.map (mkType creationAide) ts, stn ">" gt) - - DotGetAppPartNode(mkSynLongIdent s, tpi, mkExpr creationAide e, m)) - - ExprDotGetAppWithParenLambdaNode(mkExpr creationAide e, parenLambdaNode, args, exprRange) - |> Expr.DotGetAppWithParenLambda - - | DotGetApp(e, es) -> - let args = - es - |> List.map (fun (s, t, e) -> - let m = unionRanges s.Range e.Range - - let tpi = - t - |> Option.map (fun (lt, ts, gt) -> stn "<" lt, List.map (mkType creationAide) ts, stn ">" gt) - - DotGetAppPartNode(mkSynLongIdent s, tpi, mkExpr creationAide e, m)) + | ChainExpr links -> + let chainLinks = + links + |> List.map (function + | LinkExpr.Identifier identifierExpr -> mkExpr creationAide identifierExpr |> ChainLink.Identifier + | LinkExpr.Dot mDot -> stn "." mDot |> ChainLink.Dot + | LinkExpr.Expr e -> mkExpr creationAide e |> ChainLink.Expr + | LinkExpr.AppUnit(f, mUnit) -> + LinkSingleAppUnit(mkExpr creationAide f, mkUnit mUnit, unionRanges f.Range mUnit) + |> ChainLink.AppUnit + | LinkExpr.AppParen(f, lpr, e, rpr, pr) -> + LinkSingleAppParen( + mkExpr creationAide f, + mkParenExpr creationAide lpr e rpr pr, + unionRanges f.Range pr + ) + |> ChainLink.AppParen + | LinkExpr.IndexExpr e -> mkExpr creationAide e |> ChainLink.IndexExpr + | link -> failwithf "cannot map %A" link) - ExprDotGetAppNode(mkExpr creationAide e, args, exprRange) |> Expr.DotGetApp + ExprChain(chainLinks, exprRange) |> Expr.Chain | AppSingleParenArg(SynExpr.LongIdent(longDotId = longDotId), px) -> ExprAppLongIdentAndSingleParenArgNode(mkSynLongIdent longDotId, mkExpr creationAide px, exprRange) |> Expr.AppLongIdentAndSingleParenArg @@ -1268,9 +1433,6 @@ let mkExpr (creationAide: CreationAide) (e: SynExpr) : Expr = exprRange ) |> Expr.DotNamedIndexedPropertySet - | SynExpr.DotGet(e, _, synLongIdent, _) -> - ExprDotGetNode(mkExpr creationAide e, mkSynLongIdent synLongIdent, exprRange) - |> Expr.DotGet | SynExpr.DotSet(e1, synLongIdent, e2, _) -> ExprDotSetNode(mkExpr creationAide e1, mkSynLongIdent synLongIdent, mkExpr creationAide e2, exprRange) |> Expr.DotSet @@ -1405,6 +1567,8 @@ let (|PatParameter|_|) (p: SynPat) = | SynPat.Attrib(pat = pat; attributes = attributes) -> Some(attributes, pat, None) | _ -> None +let mkUnit (StartEndRange 1 (lpr, m, rpr)) = UnitNode(stn "(" lpr, stn ")" rpr, m) + let mkPat (creationAide: CreationAide) (p: SynPat) = let patternRange = p.Range @@ -1471,8 +1635,7 @@ let mkPat (creationAide: CreationAide) (p: SynPat) = patternRange ) |> Pattern.LongIdent - | SynPat.Paren(SynPat.Const(SynConst.Unit, _), StartEndRange 1 (lpr, _, rpr)) -> - UnitNode(stn "(" lpr, stn ")" rpr, patternRange) |> Pattern.Unit + | SynPat.Paren(SynPat.Const(SynConst.Unit, _), mUnit) -> mkUnit mUnit |> Pattern.Unit | SynPat.Paren(p, StartEndRange 1 (lpr, _, rpr)) -> PatParenNode(stn "(" lpr, mkPat creationAide p, stn ")" rpr, patternRange) |> Pattern.Paren diff --git a/src/Fantomas.Core/CodePrinter.fs b/src/Fantomas.Core/CodePrinter.fs index 224511c447..12b2449c75 100644 --- a/src/Fantomas.Core/CodePrinter.fs +++ b/src/Fantomas.Core/CodePrinter.fs @@ -44,11 +44,19 @@ let rec (|UppercaseExpr|LowercaseExpr|) (expr: Expr) = match expr with | Expr.Ident ident -> upperOrLower ident.Text | Expr.OptVar node -> lastFragmentInList node.Identifier - | Expr.DotGet node -> lastFragmentInList node.Identifier + | Expr.Chain node -> + match List.tryLast node.Links with + | None + | Some(ChainLink.Dot _) -> LowercaseExpr + | Some(ChainLink.Identifier e) + | Some(ChainLink.Expr e) -> (|UppercaseExpr|LowercaseExpr|) e + | Some(ChainLink.AppParen appParen) -> (|UppercaseExpr|LowercaseExpr|) appParen.FunctionName + | Some(ChainLink.AppUnit appUnit) -> (|UppercaseExpr|LowercaseExpr|) appUnit.FunctionName + // Questionable + | Some(ChainLink.IndexExpr _) -> LowercaseExpr | Expr.DotIndexedGet node -> (|UppercaseExpr|LowercaseExpr|) node.ObjectExpr | Expr.TypeApp node -> (|UppercaseExpr|LowercaseExpr|) node.Identifier | Expr.Dynamic node -> (|UppercaseExpr|LowercaseExpr|) node.FuncExpr - | Expr.DotGetAppParen node -> (|UppercaseExpr|LowercaseExpr|) node.Function | _ -> failwithf "cannot determine if Expr %A is uppercase or lowercase" expr let (|ParenExpr|_|) (e: Expr) = @@ -990,257 +998,145 @@ let genExpr (e: Expr) = +> sepCloseLFixed |> genNode node - // Result.Ok 42 - | Expr.AppDotGetTypeApp node -> - genExpr node.TypeApp.Identifier - +> genGenericTypeParameters node.TypeApp - +> genIdentListNodeWithDot node.Property - +> sepSpaceOrIndentAndNlnIfExpressionExceedsPageWidth (col sepSpace node.Arguments genExpr) - |> genNode node - - // Foo(fun x -> x).Bar().Meh - | Expr.DotGetAppDotGetAppParenLambda node -> - let short = - genExpr node.Identifier - +> genExpr node.IdentifierArg - +> genIdentListNodeWithDot node.AppLids - +> col sepComma node.Args genExpr - +> genIdentListNodeWithDot node.Property - - let long = - let functionName = - match node.Identifier with - | Expr.OptVar identifierNode when List.moreThanOne identifierNode.Identifier.Content -> - genFunctionNameWithMultilineLids sepNone identifierNode.Identifier identifierNode - | Expr.TypeApp typedAppNode -> - match typedAppNode.Identifier with - | Expr.OptVar identifierNode when List.moreThanOne identifierNode.Identifier.Content -> - genFunctionNameWithMultilineLids - (genGenericTypeParameters typedAppNode) - identifierNode.Identifier - typedAppNode - | _ -> genExpr node.Identifier - | _ -> genExpr node.Identifier - - functionName - +> indent - +> genExpr node.IdentifierArg - +> sepNln - +> genIdentListNodeWithDot node.AppLids - +> col sepComma node.Args genExpr - +> sepNln - +> genIdentListNodeWithDot node.Property - +> unindent - - fun ctx -> genNode node (isShortExpression ctx.Config.MaxDotGetExpressionWidth short long) ctx - - // Foo().Bar - | Expr.DotGetAppParen node -> - let short = - genExpr node.Function - +> genExpr node.ParenArg - +> genIdentListNodeWithDot node.Property - - let long = - let functionName argFn = - match node.Function with - | Expr.OptVar identifierNode when List.moreThanOne identifierNode.Identifier.Content -> - genFunctionNameWithMultilineLids argFn identifierNode.Identifier identifierNode - | Expr.TypeApp typedAppNode -> - match typedAppNode.Identifier with - | Expr.OptVar identifierNode when List.moreThanOne identifierNode.Identifier.Content -> - genFunctionNameWithMultilineLids - (genGenericTypeParameters typedAppNode +> argFn) - identifierNode.Identifier - typedAppNode - | _ -> genExpr node.Function +> argFn - | Expr.DotGetAppDotGetAppParenLambda _ -> - leadingExpressionIsMultiline (genExpr node.Function) (fun isMultiline -> - if isMultiline then indent +> argFn +> unindent else argFn) - | _ -> genExpr node.Function +> argFn - - let arguments = genMultilineFunctionApplicationArguments node.ParenArg - - functionName arguments - +> indentSepNlnUnindent (genIdentListNodeWithDotMultiline node.Property) - - fun ctx -> genNode node (isShortExpression ctx.Config.MaxDotGetExpressionWidth short long) ctx - - // Foo(fun x -> x).Bar() - | Expr.DotGetAppWithParenLambda node -> - let genLongFunctionName f = - match node.Function with - | Expr.OptVar identifierNode when List.moreThanOne identifierNode.Identifier.Content -> - genFunctionNameWithMultilineLids f identifierNode.Identifier identifierNode - | Expr.TypeApp typedAppNode -> - match typedAppNode.Identifier with - | Expr.OptVar identifierNode when List.moreThanOne identifierNode.Identifier.Content -> - genFunctionNameWithMultilineLids - (genGenericTypeParameters typedAppNode +> f) - identifierNode.Identifier - typedAppNode - | _ -> genExpr node.Function - | _ -> genExpr node.Function +> f - - let lastEsIndex = node.Arguments.Length - 1 - - let genApp (idx: int) (argNode: DotGetAppPartNode) : Context -> Context = - let short = - genIdentListNodeWithDot argNode.Identifier - +> optSingle (fun (lt, ts, gt) -> genGenericTypeParametersAux lt ts gt) argNode.TypeParameterInfo - +> genSpaceBeforeLids idx lastEsIndex argNode.Identifier argNode.Expr - +> genExpr argNode.Expr - - let long = - genIdentListNodeWithDotMultiline argNode.Identifier - +> optSingle (fun (lt, ts, gt) -> genGenericTypeParametersAux lt ts gt) argNode.TypeParameterInfo - +> genSpaceBeforeLids idx lastEsIndex argNode.Identifier argNode.Expr - +> genMultilineFunctionApplicationArguments argNode.Expr - - expressionFitsOnRestOfLine short long + | Expr.Chain node -> + let genLink (isLastLink: bool) (link: ChainLink) = + match link with + | ChainLink.Identifier expr -> genExpr expr + | ChainLink.Dot stn -> genSingleTextNode stn + | ChainLink.Expr expr -> + match expr with + | Expr.App appNode -> + match appNode.Arguments with + | [ Expr.ArrayOrList _ as arrayOrList ] -> + // Edge case for something like .G[]. + genExpr appNode.FunctionExpr +> genExpr arrayOrList + | _ -> genExpr expr + | _ -> genExpr expr + | ChainLink.AppUnit appUnitNode -> + genExpr appUnitNode.FunctionName + +> onlyIf + isLastLink + (sepSpaceBeforeParenInFuncInvocation + appUnitNode.FunctionName + (Expr.Constant(Constant.Unit appUnitNode.Unit))) + +> genUnit appUnitNode.Unit + |> genNode appUnitNode + | ChainLink.AppParen appParen -> + let short = + genExpr appParen.FunctionName + +> onlyIf + isLastLink + (sepSpaceBeforeParenInFuncInvocation appParen.FunctionName (Expr.Paren appParen.Paren)) + +> genExpr (Expr.Paren appParen.Paren) - let short = - genExpr node.Function - +> genExpr (Expr.ParenLambda node.ParenLambda) - +> coli sepNone node.Arguments (fun idx (argNode: DotGetAppPartNode) -> - genIdentListNodeWithDot argNode.Identifier - +> optSingle (fun (lt, ts, gt) -> genGenericTypeParametersAux lt ts gt) argNode.TypeParameterInfo - +> genSpaceBeforeLids idx lastEsIndex argNode.Identifier argNode.Expr - +> genExpr argNode.Expr) + let long = + match appParen.Paren.Expr with + | Expr.Lambda lambdaNode -> + genExpr appParen.FunctionName + +> onlyIf + isLastLink + (sepSpaceBeforeParenInFuncInvocation appParen.FunctionName (Expr.Paren appParen.Paren)) + +> genSingleTextNode appParen.Paren.OpeningParen + +> genLambdaWithParen lambdaNode + +> onlyIfCtx (fun ctx -> ctx.Config.MultiLineLambdaClosingNewline) sepNln + +> genSingleTextNode appParen.Paren.ClosingParen + | _ -> + genExpr appParen.FunctionName + +> onlyIf + isLastLink + (sepSpaceBeforeParenInFuncInvocation appParen.FunctionName (Expr.Paren appParen.Paren)) + +> genMultilineFunctionApplicationArguments (Expr.Paren appParen.Paren) + + expressionFitsOnRestOfLine short long |> genNode appParen + | ChainLink.IndexExpr e -> sepOpenLFixed +> genExpr e +> sepCloseLFixed + + let lastIndex = node.Links.Length - 1 + let short = coli sepNone node.Links (fun idx -> genLink (idx = lastIndex)) let long = - genLongFunctionName (genExpr (Expr.ParenLambda node.ParenLambda)) - +> indent - +> sepNln - +> coli sepNln node.Arguments genApp - +> unindent - - fun ctx -> genNode node (isShortExpression ctx.Config.MaxDotGetExpressionWidth short long) ctx - - // Foo().Bar().Meh() - | Expr.DotGetApp node -> - let genLongFunctionName = - let genApp funcExpr argExpr appNode = - match funcExpr, argExpr with - // | AppOrTypeApp(LongIdentExprWithMoreThanOneIdent lids, t, [ Paren _ as px ]) -> - | Expr.OptVar optVarNode, [ ParenExpr px ] when List.moreThanOne optVarNode.Identifier.Content -> - genFunctionNameWithMultilineLids - (expressionFitsOnRestOfLine (genExpr px) (genMultilineFunctionApplicationArguments px)) - optVarNode.Identifier - optVarNode - - | Expr.TypeApp typeAppNode, [ ParenExpr px ] -> - match typeAppNode.Identifier with - | Expr.OptVar optVarNode when List.moreThanOne optVarNode.Identifier.Content -> - genFunctionNameWithMultilineLids - (genGenericTypeParameters typeAppNode - +> expressionFitsOnRestOfLine (genExpr px) (genMultilineFunctionApplicationArguments px)) - optVarNode.Identifier - optVarNode - | Expr.Ident _ -> - genExpr typeAppNode.Identifier - +> genGenericTypeParameters typeAppNode - +> genExpr px - | _ -> genExpr node.FunctionExpr - - // | AppOrTypeApp(LongIdentExprWithMoreThanOneIdent lids, t, [ e2 ]) -> - | Expr.OptVar optVarNode, [ e2 ] when List.moreThanOne optVarNode.Identifier.Content -> - genFunctionNameWithMultilineLids (genExpr e2) optVarNode.Identifier optVarNode - - | Expr.TypeApp typeAppNode, [ e2 ] -> - match typeAppNode.Identifier with - | Expr.OptVar optVarNode when List.moreThanOne optVarNode.Identifier.Content -> - genFunctionNameWithMultilineLids - (genGenericTypeParameters typeAppNode +> genExpr e2) - optVarNode.Identifier - optVarNode - | _ -> genExpr node.FunctionExpr - - // | AppOrTypeApp(SimpleExpr e, t, [ ConstExpr(SynConst.Unit, r) ]) -> - | Expr.Ident _, [ Expr.Constant(Constant.Unit _) as unitExpr ] -> genExpr funcExpr +> genExpr unitExpr - - | Expr.TypeApp typeAppNode, [ Expr.Constant(Constant.Unit _) as unitExpr ] -> - match typeAppNode.Identifier with - | Expr.Ident _ -> - genExpr typeAppNode.Identifier - +> genGenericTypeParameters typeAppNode - +> genExpr unitExpr - | _ -> genExpr node.FunctionExpr - - // | AppOrTypeApp(SimpleExpr e, t, [ Paren _ as px ]) -> - | Expr.Ident _, [ ParenExpr parenExpr ] -> - let short = genExpr funcExpr +> genExpr parenExpr - let long = genExpr funcExpr +> genMultilineFunctionApplicationArguments parenExpr - expressionFitsOnRestOfLine short long - - | Expr.TypeApp typeAppNode, [ ParenExpr parenExpr ] -> - match typeAppNode.Identifier with - | Expr.Ident _ -> - let short = - genExpr typeAppNode.Identifier - +> genGenericTypeParameters typeAppNode - +> genExpr parenExpr - - let long = - genExpr typeAppNode.Identifier - +> genGenericTypeParameters typeAppNode - +> genMultilineFunctionApplicationArguments parenExpr - - expressionFitsOnRestOfLine short long - | _ -> genExpr node.FunctionExpr - - | _ -> genExpr node.FunctionExpr - |> genNode appNode - - match node.FunctionExpr with - | Expr.App appNode -> genApp appNode.FunctionExpr appNode.Arguments appNode - | Expr.AppLongIdentAndSingleParenArg appNode -> - let m = (appNode.FunctionName :> Node).Range - genApp (Expr.OptVar(ExprOptVarNode(false, appNode.FunctionName, m))) [ appNode.ArgExpr ] appNode - | Expr.AppSingleParenArg appNode -> genApp appNode.FunctionExpr [ appNode.ArgExpr ] appNode - | _ -> genExpr node.FunctionExpr - - let lastEsIndex = node.Arguments.Length - 1 - - let genApp (idx: int) (argNode: DotGetAppPartNode) : Context -> Context = - let short = - genIdentListNodeWithDot argNode.Identifier - +> optSingle (fun (lt, ts, gt) -> genGenericTypeParametersAux lt ts gt) argNode.TypeParameterInfo - +> genSpaceBeforeLids idx lastEsIndex argNode.Identifier argNode.Expr - +> genExpr argNode.Expr - - let long = - genIdentListNodeWithDotMultiline argNode.Identifier - +> optSingle (fun (lt, ts, gt) -> genGenericTypeParametersAux lt ts gt) argNode.TypeParameterInfo - +> genSpaceBeforeLids idx lastEsIndex argNode.Identifier argNode.Expr - +> genMultilineFunctionApplicationArguments argNode.Expr - - expressionFitsOnRestOfLine short long |> genNode argNode + let (|SimpleChain|_|) (link: ChainLink) = + match link with + | ChainLink.Identifier _ + | ChainLink.IndexExpr _ -> Some link + | _ -> None + + let (|LeadingSimpleChain|_|) (links: ChainLink list) = + let leading = System.Collections.Generic.Queue(links.Length) + let rest = System.Collections.Generic.Queue(links.Length) + + (None, links) + ||> List.fold (fun lastDot link -> + if not (Seq.isEmpty rest) then + rest.Enqueue link + None + else + match link with + | SimpleChain _ -> + Option.iter leading.Enqueue lastDot + leading.Enqueue link + None + | ChainLink.Dot _ as dot -> Some dot + | _ -> + Option.iter rest.Enqueue lastDot + rest.Enqueue link + None) + |> (fun _ -> + if Seq.isEmpty leading then + None + else + Some(Seq.toList leading, Seq.toList rest)) - let short = - match node.FunctionExpr with - | Expr.AppLongIdentAndSingleParenArg appNode -> - genIdentListNode appNode.FunctionName +> genExpr appNode.ArgExpr - | Expr.AppSingleParenArg appNode -> genExpr appNode.FunctionExpr +> genExpr appNode.ArgExpr - | Expr.App appNode -> - if appNode.Arguments.Length = 1 then - match appNode.Arguments.[0] with - | ParenExpr parenExpr -> genExpr appNode.FunctionExpr +> genExpr parenExpr - | Expr.ArrayOrList _ -> genExpr appNode.FunctionExpr +> genExpr appNode.Arguments.[0] - | _ -> genExpr node.FunctionExpr - else - genExpr node.FunctionExpr - | _ -> genExpr node.FunctionExpr - +> coli sepNone node.Arguments genApp + let rec genIndentedLinks (lastLinkWasSimple: bool) (links: ChainLink list) (ctx: Context) : Context = + match links with + | [] -> ctx + | ChainLink.Dot dot :: link :: rest -> + let isLast = List.isEmpty rest + let genDotAndLink = genSingleTextNode dot +> genLink isLast link + let currentIsSimple = ((|SimpleChain|_|) >> Option.isSome) link + + if lastLinkWasSimple && not (futureNlnCheck genDotAndLink ctx) then + // The last link was an identifier and the current link fits on the remainder of the current line. + genIndentedLinks currentIsSimple rest ((genDotAndLink +> onlyIfNot isLast sepNln) ctx) + else + genIndentedLinks + currentIsSimple + rest + // Print the current link + ((genDotAndLink + // Don't suffix with a newline if we are at the end of the chain, + // or if the current link is an identifier. + +> onlyIfNot (isLast || currentIsSimple) sepNln) + ctx) + | _ -> failwith "Expected dot in chain at this point" + + let genFirstLinkAndIndentOther (firstLink: ChainLink) (others: ChainLink list) = + genLink false firstLink +> indentSepNlnUnindent (genIndentedLinks false others) + + match node.Links with + | [] -> sepNone + | LeadingSimpleChain(leadingChain, links) -> + match links with + | [] -> + fun ctx -> + isShortExpression + ctx.Config.MaxDotGetExpressionWidth + short + (match leadingChain with + | [] -> sepNone + | head :: links -> genFirstLinkAndIndentOther head links) + ctx + | _ -> + expressionFitsOnRestOfLine + (coli sepNone leadingChain (fun idx -> genLink (idx = lastIndex))) + (match leadingChain with + | [] -> sepNone + | head :: rest -> genLink false head +> indentSepNlnUnindent (genIndentedLinks false rest)) + +> indentSepNlnUnindent (genIndentedLinks false links) - let long = - genLongFunctionName - +> indent - +> sepNln - +> coli sepNln node.Arguments genApp - +> unindent + | head :: links -> genFirstLinkAndIndentOther head links - fun ctx -> genNode node (isShortExpression ctx.Config.MaxDotGetExpressionWidth short long) ctx + (fun ctx -> isShortExpression ctx.Config.MaxDotGetExpressionWidth short long ctx) + |> genNode node // path.Replace("../../../", "....") | Expr.AppLongIdentAndSingleParenArg node -> @@ -1735,14 +1631,6 @@ let genExpr (e: Expr) = +> sepArrowRev +> autoIndentAndNlnIfExpressionExceedsPageWidth (genExpr node.Set) |> genNode node - | Expr.DotGet node -> - let shortExpr = genExpr node.Expr +> genIdentListNodeWithDot node.Identifier - - let longExpr = - genExpr node.Expr - +> indentSepNlnUnindent (genIdentListNodeWithDotMultiline node.Identifier) - - fun ctx -> genNode node (isShortExpression ctx.Config.MaxDotGetExpressionWidth shortExpr longExpr) ctx | Expr.DotSet node -> match node.Identifier with | Expr.AppLongIdentAndSingleParenArg appNode -> @@ -2168,9 +2056,9 @@ let genExprInMultilineInfixExpr (e: Expr) = ctx | Expr.InfixApp infixNode -> match infixNode.LeftHandSide with - | Expr.DotGet _ -> atCurrentColumnIndent (genExpr e) + | Expr.Chain _ -> atCurrentColumnIndent (genExpr e) | _ -> genExpr e - | Expr.DotGetApp _ + | Expr.Chain _ | Expr.Record _ -> atCurrentColumnIndent (genExpr e) | _ -> genExpr e | Expr.MatchLambda matchLambdaNode -> @@ -2388,33 +2276,6 @@ let sepSpaceBeforeParenInFuncInvocation (functionExpr: Expr) (argExpr: Expr) ctx | Expr.Ident _, Expr.Ident _ -> sepSpace ctx | _ -> sepSpace ctx -let genSpaceBeforeLids - (currentIndex: int) - (lastEsIndex: int) - (lids: IdentListNode) - (arg: Expr) - (ctx: Context) - : Context = - let config = - match lids.Content with - | IdentifierOrDot.Ident h :: _ -> - let s = h.Text - - if Char.IsUpper(s.[0]) then - ctx.Config.SpaceBeforeUppercaseInvocation - else - ctx.Config.SpaceBeforeLowercaseInvocation - | _ -> ctx.Config.SpaceBeforeUppercaseInvocation - - let hasParenthesis = - match arg with - | ParenExpr _ -> true - | _ -> false - - if (lastEsIndex = currentIndex) && (not hasParenthesis || config) then - sepSpace ctx - else - ctx // end expressions let genPatLeftMiddleRight (node: PatLeftMiddleRight) = diff --git a/src/Fantomas.Core/Selection.fs b/src/Fantomas.Core/Selection.fs index 768d5f58bd..e75793fd83 100644 --- a/src/Fantomas.Core/Selection.fs +++ b/src/Fantomas.Core/Selection.fs @@ -231,20 +231,8 @@ let mkTreeWithSingleNode (node: Node) : TreeForSelection = | :? ExprIndexWithoutDotNode as node -> let expr = Expr.IndexWithoutDot node mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) - | :? ExprAppDotGetTypeAppNode as node -> - let expr = Expr.AppDotGetTypeApp node - mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) - | :? ExprDotGetAppDotGetAppParenLambdaNode as node -> - let expr = Expr.DotGetAppDotGetAppParenLambda node - mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) - | :? ExprDotGetAppParenNode as node -> - let expr = Expr.DotGetAppParen node - mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) - | :? ExprDotGetAppWithParenLambdaNode as node -> - let expr = Expr.DotGetAppWithParenLambda node - mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) - | :? ExprDotGetAppNode as node -> - let expr = Expr.DotGetApp node + | :? ExprChain as node -> + let expr = Expr.Chain node mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) | :? ExprAppLongIdentAndSingleParenArgNode as node -> let expr = Expr.AppLongIdentAndSingleParenArg node @@ -309,9 +297,6 @@ let mkTreeWithSingleNode (node: Node) : TreeForSelection = | :? ExprDotNamedIndexedPropertySetNode as node -> let expr = Expr.DotNamedIndexedPropertySet node mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) - | :? ExprDotGetNode as node -> - let expr = Expr.DotGet node - mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) | :? ExprDotSetNode as node -> let expr = Expr.DotSet node mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) diff --git a/src/Fantomas.Core/SyntaxOak.fs b/src/Fantomas.Core/SyntaxOak.fs index 98affa418d..31d09e9595 100644 --- a/src/Fantomas.Core/SyntaxOak.fs +++ b/src/Fantomas.Core/SyntaxOak.fs @@ -1152,88 +1152,43 @@ type ExprIndexWithoutDotNode(identifierExpr: Expr, indexExpr: Expr, range) = member x.Identifier = identifierExpr member x.Index = indexExpr -type ExprAppDotGetTypeAppNode(typeApp: ExprTypeAppNode, property: IdentListNode, arguments: Expr list, range) = +type LinkSingleAppParen(functionName: Expr, parenExpr: ExprParenNode, range) = inherit NodeBase(range) + override this.Children = [| yield Expr.Node functionName; yield parenExpr |] + member x.FunctionName = functionName + member x.Paren = parenExpr - override this.Children = - [| yield typeApp; yield property; yield! List.map Expr.Node arguments |] - - member x.TypeApp = typeApp - member x.Property = property - member x.Arguments = arguments - -type ExprDotGetAppDotGetAppParenLambdaNode - ( - identifierExpr: Expr, - identifierArgExpr: Expr, - appLids: IdentListNode, - es: Expr list, - property: IdentListNode, - range - ) = - inherit NodeBase(range) - - override this.Children = - [| yield Expr.Node identifierExpr - yield Expr.Node identifierArgExpr - yield appLids - yield! List.map Expr.Node es - yield property |] - - member x.Identifier = identifierExpr - member x.IdentifierArg = identifierArgExpr - member x.AppLids = appLids - member x.Args = es - member x.Property = property - -type ExprDotGetAppParenNode(funcExpr: Expr, parenExpr: Expr, property: IdentListNode, range) = - inherit NodeBase(range) - - override this.Children = - [| yield Expr.Node funcExpr; yield Expr.Node parenExpr; yield property |] - - member x.Function = funcExpr - member x.ParenArg = parenExpr - member x.Property = property - -type DotGetAppPartNode - ( - identifier: IdentListNode, - typeParameterInfo: (SingleTextNode * Type list * SingleTextNode) option, - expr: Expr, - range: range - ) = - inherit NodeBase(range) - - override this.Children = - let typeParameterInfoNodes = - match typeParameterInfo with - | None -> [||] - | Some(lt, ts, gt) -> [| yield (lt :> Node); yield! List.map Type.Node ts; yield gt |] - - [| yield identifier; yield! typeParameterInfoNodes; yield Expr.Node expr |] - - member x.Identifier = identifier - member x.TypeParameterInfo = typeParameterInfo - member x.Expr = expr - -type ExprDotGetAppWithParenLambdaNode - (funcExpr: Expr, parenLambda: ExprParenLambdaNode, args: DotGetAppPartNode list, range) = - inherit NodeBase(range) - - override this.Children = - [| yield Expr.Node funcExpr; yield parenLambda; yield! nodes args |] - - member x.Function = funcExpr - member x.ParenLambda = parenLambda - member x.Arguments = args - -type ExprDotGetAppNode(funcExpr: Expr, args: DotGetAppPartNode list, range) = +type LinkSingleAppUnit(functionName: Expr, unit: UnitNode, range) = inherit NodeBase(range) + override this.Children = [| yield Expr.Node functionName; yield unit |] + member x.FunctionName = functionName + member x.Unit = unit - override this.Children = [| yield Expr.Node funcExpr; yield! nodes args |] - member x.FunctionExpr = funcExpr - member x.Arguments = args +[] +type ChainLink = + | Identifier of Expr + | Dot of SingleTextNode + | Expr of Expr + | AppParen of + // There should only be one argument + LinkSingleAppParen + | AppUnit of LinkSingleAppUnit + // [ expr ] from DotIndexedGet + | IndexExpr of Expr // e.[f] + + static member Node(link: ChainLink) : Node = + match link with + | Identifier e -> Expr.Node e + | Dot n -> n + | Expr e -> Expr.Node e + | AppParen n -> n + | AppUnit n -> n + | IndexExpr e -> Expr.Node e + +type ExprChain(links: ChainLink list, range) = + inherit NodeBase(range) + override this.Children = List.map ChainLink.Node links |> List.toArray + member x.Links = links type ExprAppLongIdentAndSingleParenArgNode(functionName: IdentListNode, argExpr: Expr, range) = inherit NodeBase(range) @@ -1577,12 +1532,6 @@ type ExprDotNamedIndexedPropertySetNode member x.Property = propertyExpr member x.Set = setExpr -type ExprDotGetNode(expr: Expr, identifier: IdentListNode, range) = - inherit NodeBase(range) - override this.Children = [| yield Expr.Node expr; yield identifier |] - member x.Expr = expr - member x.Identifier = identifier - type ExprDotSetNode(identifier: Expr, property: IdentListNode, setExpr: Expr, range) = inherit NodeBase(range) @@ -1724,11 +1673,6 @@ type Expr = | SameInfixApps of ExprSameInfixAppsNode | InfixApp of ExprInfixAppNode | IndexWithoutDot of ExprIndexWithoutDotNode - | AppDotGetTypeApp of ExprAppDotGetTypeAppNode - | DotGetAppDotGetAppParenLambda of ExprDotGetAppDotGetAppParenLambdaNode - | DotGetAppParen of ExprDotGetAppParenNode - | DotGetAppWithParenLambda of ExprDotGetAppWithParenLambdaNode - | DotGetApp of ExprDotGetAppNode | AppLongIdentAndSingleParenArg of ExprAppLongIdentAndSingleParenArgNode | AppSingleParenArg of ExprAppSingleParenArgNode | DotGetAppWithLambda of ExprDotGetAppWithLambdaNode @@ -1751,7 +1695,6 @@ type Expr = | DotIndexedSet of ExprDotIndexedSetNode | NamedIndexedPropertySet of ExprNamedIndexedPropertySetNode | DotNamedIndexedPropertySet of ExprDotNamedIndexedPropertySetNode - | DotGet of ExprDotGetNode | DotSet of ExprDotSetNode | Set of ExprSetNode | LibraryOnlyStaticOptimization of ExprLibraryOnlyStaticOptimizationNode @@ -1761,6 +1704,7 @@ type Expr = | IndexRange of ExprIndexRangeNode | IndexFromEnd of ExprIndexFromEndNode | Typar of SingleTextNode + | Chain of ExprChain static member Node(x: Expr) : Node = match x with @@ -1797,11 +1741,6 @@ type Expr = | SameInfixApps n -> n | InfixApp n -> n | IndexWithoutDot n -> n - | AppDotGetTypeApp n -> n - | DotGetAppDotGetAppParenLambda n -> n - | DotGetAppParen n -> n - | DotGetAppWithParenLambda n -> n - | DotGetApp n -> n | AppLongIdentAndSingleParenArg n -> n | AppSingleParenArg n -> n | DotGetAppWithLambda n -> n @@ -1824,7 +1763,6 @@ type Expr = | DotIndexedSet n -> n | NamedIndexedPropertySet n -> n | DotNamedIndexedPropertySet n -> n - | DotGet n -> n | DotSet n -> n | Set n -> n | LibraryOnlyStaticOptimization n -> n @@ -1834,6 +1772,7 @@ type Expr = | IndexRange n -> n | IndexFromEnd n -> n | Typar n -> n + | Chain n -> n member e.IsStroustrupStyleExpr: bool = match e with From 851631fa08c607de7f96c682a4c7fbe1db460d59 Mon Sep 17 00:00:00 2001 From: nojaf Date: Mon, 9 Jan 2023 15:48:07 +0100 Subject: [PATCH 2/7] Update test result according to proposal. --- src/Fantomas.Core.Tests/DotGetTests.fs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Fantomas.Core.Tests/DotGetTests.fs b/src/Fantomas.Core.Tests/DotGetTests.fs index 8eafc1a035..4b7545d2d5 100644 --- a/src/Fantomas.Core.Tests/DotGetTests.fs +++ b/src/Fantomas.Core.Tests/DotGetTests.fs @@ -1338,6 +1338,8 @@ Assembly.GetExecutingAssembly().GetCustomAttribute().SomeProp |> should equal """ -Assembly.GetExecutingAssembly().GetCustomAttribute() +Assembly + .GetExecutingAssembly() + .GetCustomAttribute() .SomeProperty """ From d51cb01851efe5d80ccc8551dcf3f885a272066d Mon Sep 17 00:00:00 2001 From: nojaf Date: Mon, 9 Jan 2023 15:48:20 +0100 Subject: [PATCH 3/7] Trivia inside chain. --- src/Fantomas.Core.Tests/ChainTests.fs | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/Fantomas.Core.Tests/ChainTests.fs b/src/Fantomas.Core.Tests/ChainTests.fs index 6fe2a92ed5..0872e02e88 100644 --- a/src/Fantomas.Core.Tests/ChainTests.fs +++ b/src/Fantomas.Core.Tests/ChainTests.fs @@ -275,3 +275,40 @@ A.B .C(D) .E.[0] """ + +[] +let ``trivia inside chain, 2686`` () = + formatSourceString + false + """ +builder. + FirstThing(fun lambda -> + // aaaaaa + () + ) + .SecondThing(fun next -> + // bbbbb + next + ) + // ccccc + .ThirdThing().X +""" + { config with + MultiLineLambdaClosingNewline = true } + |> prepend newline + |> should + equal + """ +builder + .FirstThing(fun lambda -> + // aaaaaa + () + ) + .SecondThing(fun next -> + // bbbbb + next + ) + // ccccc + .ThirdThing() + .X +""" From 50ad23c291337b7b60efa1bc0b06f8b6f3418256 Mon Sep 17 00:00:00 2001 From: nojaf Date: Tue, 10 Jan 2023 21:20:13 +0100 Subject: [PATCH 4/7] Remove obsolete ExprDotGetAppWithLambdaNode --- src/Fantomas.Core/ASTTransformer.fs | 17 ----------------- src/Fantomas.Core/CodePrinter.fs | 7 ------- src/Fantomas.Core/Selection.fs | 3 --- src/Fantomas.Core/SyntaxOak.fs | 9 --------- 4 files changed, 36 deletions(-) diff --git a/src/Fantomas.Core/ASTTransformer.fs b/src/Fantomas.Core/ASTTransformer.fs index 6e31b77fec..6856be4802 100644 --- a/src/Fantomas.Core/ASTTransformer.fs +++ b/src/Fantomas.Core/ASTTransformer.fs @@ -1184,23 +1184,6 @@ let mkExpr (creationAide: CreationAide) (e: SynExpr) : Expr = | AppSingleParenArg(e, px) -> ExprAppSingleParenArgNode(mkExpr creationAide e, mkExpr creationAide px, exprRange) |> Expr.AppSingleParenArg - | SynExpr.DotGet( - expr = SynExpr.App(funcExpr = App(fe, args); argExpr = ParenLambda(lpr, pats, mArrow, body, mLambda, rpr)) - longDotId = lid) -> - let lambdaNode = mkLambda creationAide pats mArrow body mLambda - - let appWithLambdaNode = - ExprAppWithLambdaNode( - mkExpr creationAide fe, - List.map (mkExpr creationAide) args, - stn "(" lpr, - Choice1Of2 lambdaNode, - stn ")" rpr, - exprRange - ) - - ExprDotGetAppWithLambdaNode(appWithLambdaNode, mkSynLongIdent lid, exprRange) - |> Expr.DotGetAppWithLambda | SynExpr.App(funcExpr = App(fe, args); argExpr = ParenLambda(lpr, pats, mArrow, body, mLambda, rpr)) -> let lambdaNode = mkLambda creationAide pats mArrow body mLambda diff --git a/src/Fantomas.Core/CodePrinter.fs b/src/Fantomas.Core/CodePrinter.fs index 12b2449c75..691f1d87dc 100644 --- a/src/Fantomas.Core/CodePrinter.fs +++ b/src/Fantomas.Core/CodePrinter.fs @@ -1200,13 +1200,6 @@ let genExpr (e: Expr) = expressionFitsOnRestOfLine short long |> genNode node // functionName arg1 arg2 (fun x y z -> ...) - | Expr.DotGetAppWithLambda node -> - leadingExpressionIsMultiline (genAppWithLambda sepNone node.AppWithLambda) (fun isMultiline -> - if isMultiline then - (indent +> sepNln +> genIdentListNodeWithDotMultiline node.Property +> unindent) - else - genIdentListNodeWithDot node.Property) - |> genNode node | Expr.AppWithLambda node -> let sepSpaceAfterFunctionName = match node.Arguments with diff --git a/src/Fantomas.Core/Selection.fs b/src/Fantomas.Core/Selection.fs index e75793fd83..a03dd8a67b 100644 --- a/src/Fantomas.Core/Selection.fs +++ b/src/Fantomas.Core/Selection.fs @@ -240,9 +240,6 @@ let mkTreeWithSingleNode (node: Node) : TreeForSelection = | :? ExprAppSingleParenArgNode as node -> let expr = Expr.AppSingleParenArg node mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) - | :? ExprDotGetAppWithLambdaNode as node -> - let expr = Expr.DotGetAppWithLambda node - mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) | :? ExprAppWithLambdaNode as node -> let expr = Expr.AppWithLambda node mkOakFromModuleDecl (ModuleDecl.DeclExpr expr) diff --git a/src/Fantomas.Core/SyntaxOak.fs b/src/Fantomas.Core/SyntaxOak.fs index 31d09e9595..c32082f748 100644 --- a/src/Fantomas.Core/SyntaxOak.fs +++ b/src/Fantomas.Core/SyntaxOak.fs @@ -1203,13 +1203,6 @@ type ExprAppSingleParenArgNode(functionExpr: Expr, argExpr: Expr, range) = member x.FunctionExpr = functionExpr member x.ArgExpr = argExpr -type ExprDotGetAppWithLambdaNode(appWithLambda: ExprAppWithLambdaNode, property: IdentListNode, range) = - inherit NodeBase(range) - - override this.Children = [| yield appWithLambda; yield property |] - member x.AppWithLambda = appWithLambda - member x.Property = property - type ExprAppWithLambdaNode ( functionName: Expr, @@ -1675,7 +1668,6 @@ type Expr = | IndexWithoutDot of ExprIndexWithoutDotNode | AppLongIdentAndSingleParenArg of ExprAppLongIdentAndSingleParenArgNode | AppSingleParenArg of ExprAppSingleParenArgNode - | DotGetAppWithLambda of ExprDotGetAppWithLambdaNode | AppWithLambda of ExprAppWithLambdaNode | NestedIndexWithoutDot of ExprNestedIndexWithoutDotNode | EndsWithDualListApp of ExprEndsWithDualListAppNode @@ -1743,7 +1735,6 @@ type Expr = | IndexWithoutDot n -> n | AppLongIdentAndSingleParenArg n -> n | AppSingleParenArg n -> n - | DotGetAppWithLambda n -> n | AppWithLambda n -> n | NestedIndexWithoutDot n -> n | EndsWithDualListApp n -> n From d551328be5891cb593e7dd3c3760b45b2000b3b7 Mon Sep 17 00:00:00 2001 From: nojaf Date: Tue, 10 Jan 2023 21:38:22 +0100 Subject: [PATCH 5/7] Excluded ExprAppWithLambdaNode in Chain. --- src/Fantomas.Core.Tests/LambdaTests.fs | 26 ++++++++++++++++++++++++++ src/Fantomas.Core/ASTTransformer.fs | 7 +++++++ 2 files changed, 33 insertions(+) diff --git a/src/Fantomas.Core.Tests/LambdaTests.fs b/src/Fantomas.Core.Tests/LambdaTests.fs index 3b1c65d3db..4a585982ad 100644 --- a/src/Fantomas.Core.Tests/LambdaTests.fs +++ b/src/Fantomas.Core.Tests/LambdaTests.fs @@ -1497,3 +1497,29 @@ let _ = b\" ) |> List.length " + +[] +let ``lambda with generic argument in function identifier, 2699`` () = + formatSourceString + false + """ +MailboxProcessor.Start + (fun inbox -> + async { + while true do + let! msg = inbox.Receive() + do! sw.WriteLineAsync(msg) |> Async.AwaitTask + }) +""" + config + |> prepend newline + |> should + equal + """ +MailboxProcessor.Start(fun inbox -> + async { + while true do + let! msg = inbox.Receive() + do! sw.WriteLineAsync(msg) |> Async.AwaitTask + }) +""" diff --git a/src/Fantomas.Core/ASTTransformer.fs b/src/Fantomas.Core/ASTTransformer.fs index 6856be4802..0e8dff9e3b 100644 --- a/src/Fantomas.Core/ASTTransformer.fs +++ b/src/Fantomas.Core/ASTTransformer.fs @@ -840,6 +840,13 @@ let (|ChainExpr|_|) (e: SynExpr) : LinkExpr list option = | other -> continuation [ LinkExpr.Expr other ] match e with + // An identifier only application with a parenthesis lambda expression. + // ex: `List.map (fun n -> n)` or `MailboxProcessor.Start (fun n -> n) + // Because the identifier is not complex we don't consider it a chain. + | SynExpr.App( + isInfix = false + funcExpr = SynExpr.LongIdent _ | SynExpr.Ident _ | SynExpr.DotGet(expr = SynExpr.TypeApp(expr = SynExpr.Ident _)) + argExpr = ParenExpr(_, SynExpr.Lambda _, _, _)) -> None | SynExpr.App( isInfix = false funcExpr = SynExpr.DotGet _ | SynExpr.TypeApp(expr = SynExpr.DotGet _) From baf75b9382def1f68e5719340d2fa19992221977 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Thu, 12 Jan 2023 08:21:15 +0100 Subject: [PATCH 6/7] Update src/Fantomas.Core.Tests/ChainTests.fs Co-authored-by: dawe --- src/Fantomas.Core.Tests/ChainTests.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fantomas.Core.Tests/ChainTests.fs b/src/Fantomas.Core.Tests/ChainTests.fs index 0872e02e88..d45bb73fbc 100644 --- a/src/Fantomas.Core.Tests/ChainTests.fs +++ b/src/Fantomas.Core.Tests/ChainTests.fs @@ -172,7 +172,7 @@ A().B(fun b -> b) """ [] -let ``2685 `` () = +let ``space before lambda should not occur in chain, 2685 `` () = formatSourceString false """ From d51bae07bf75744e43d85755ec6c29c3bc393ace Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 12 Jan 2023 21:36:32 +0100 Subject: [PATCH 7/7] Add release notes for 5.2.0-alpha-011. --- CHANGELOG.md | 10 +++--- src/Fantomas.Core.Tests/ChainTests.fs | 32 ------------------- .../SpaceBeforeUppercaseInvocationTests.fs | 15 +++++++++ src/Fantomas.Core.Tests/TestHelpers.fs | 8 ++--- 4 files changed, 23 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9226b89112..259f0ab223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## [5.2.0-alpha-011] - 2023-01-12 ### Fixes * Stroustrup: Two lists given directly as parameters, break code [#2681](https://github.com/fsprojects/fantomas/issues/2681) @@ -10,11 +10,9 @@ * Piped multiline application is indented too far. [#2682](https://github.com/fsprojects/fantomas/issues/2682) * Comment not assigned to first parameter in constructor. [#2692](https://github.com/fsprojects/fantomas/issues/2692) * Stroustrup: Type alias for anonymous record type. [#2179](https://github.com/fsprojects/fantomas/issues/2179) - -## [5.2.0-beta-001] - 2023-01-02 - -### Changed -* Consider v5.2 to be stable and production ready. +* Space before lambda should not occur in chain. [#2685](https://github.com/fsprojects/fantomas/issues/2685) +* Trivia inside chained lambda is not restored correctly. [#2686](https://github.com/fsprojects/fantomas/issues/2686) +* SpaceBeforeUppercaseInvocation not respected in TypeApp DotGet. [#2700](https://github.com/fsprojects/fantomas/issues/2700) ## [5.2.0-alpha-010] - 2022-12-30 diff --git a/src/Fantomas.Core.Tests/ChainTests.fs b/src/Fantomas.Core.Tests/ChainTests.fs index d45bb73fbc..121a75fd43 100644 --- a/src/Fantomas.Core.Tests/ChainTests.fs +++ b/src/Fantomas.Core.Tests/ChainTests.fs @@ -171,38 +171,6 @@ A().B(fun b -> b) A().B(fun b -> b) """ -[] -let ``space before lambda should not occur in chain, 2685 `` () = - formatSourceString - false - """ -module A = - let foo = - Foai.SomeLongTextYikes().ConfigureBarry(fun alpha beta gamma -> - context.AddSomething ("a string") |> ignore - ).MoreContext(fun builder -> - // also good stuff - () - ).ABC().XYZ -""" - { config with - SpaceBeforeUppercaseInvocation = true } - |> prepend newline - |> should - equal - """ -module A = - let foo = - Foai - .SomeLongTextYikes() - .ConfigureBarry(fun alpha beta gamma -> context.AddSomething ("a string") |> ignore) - .MoreContext(fun builder -> - // also good stuff - ()) - .ABC() - .XYZ -""" - [] let ``identifier dot appUnit dot typed appUnit `` () = formatSourceString diff --git a/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs b/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs index 0d2437eeef..85daf3d8fa 100644 --- a/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs +++ b/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs @@ -342,3 +342,18 @@ module A = .ABC() .XYZ """ + +[] +let ``typeApp with dotGet and paren expr, 2700`` () = + formatSourceString + false + """ +let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) +""" + config + |> prepend newline + |> should + equal + """ +let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(mapping) +""" diff --git a/src/Fantomas.Core.Tests/TestHelpers.fs b/src/Fantomas.Core.Tests/TestHelpers.fs index 2242291652..cc35bd77df 100644 --- a/src/Fantomas.Core.Tests/TestHelpers.fs +++ b/src/Fantomas.Core.Tests/TestHelpers.fs @@ -35,10 +35,10 @@ let formatSourceString isFsiFile (s: string) config = CodeFormatter.FormatASTAsync(ast, config = config) - // let! isValid = CodeFormatter.IsValidFSharpCodeAsync(isFsiFile, formatted) - // - // if not isValid then - // failwithf $"The formatted result is not valid F# code or contains warnings\n%s{formatted}" + let! isValid = CodeFormatter.IsValidFSharpCodeAsync(isFsiFile, formatted) + + if not isValid then + failwithf $"The formatted result is not valid F# code or contains warnings\n%s{formatted}" return formatted.Replace("\r\n", "\n") }