diff --git a/docs/FormattingConventions.md b/docs/FormattingConventions.md index e9cd945b88..7f207df8c6 100644 --- a/docs/FormattingConventions.md +++ b/docs/FormattingConventions.md @@ -527,6 +527,23 @@ Avoid extraneous whitespace in the following situations: - Method definitions inside a class are separated by a single blank line. - Extra blank lines may be used (sparingly) to separate groups of related functions. Blank lines may be omitted between a bunch of related one-liners (e.g. a set of dummy implementations). - Use blank lines in functions, sparingly, to indicate logical sections. + +#### 2020 Revision + +Blank lines are introduces around any multiline code constructs: +```fsharp +let a = 9 + +if someCondition then + printfn "meh" + () + +let b = 10 +let c = 10 +``` + +The `SynExpr.IfThenElse` expression is multiline so a blank line between `let a` and `if someCondition` and between `if someCondition` and `let b` is fitting. +Single line statements are combined without any additional blank lines, see `let b` and `let c`. ### Comments diff --git a/src/Fantomas.Tests/ActivePatternTests.fs b/src/Fantomas.Tests/ActivePatternTests.fs index 972d9350ee..18f44a1c41 100644 --- a/src/Fantomas.Tests/ActivePatternTests.fs +++ b/src/Fantomas.Tests/ActivePatternTests.fs @@ -55,6 +55,7 @@ let (|Integer|_|) (str: string) = let (|ParseRegex|_|) regex str = let m = Regex(regex).Match(str) + if m.Success then Some(List.tail [ for x in m.Groups -> x.Value ]) else None diff --git a/src/Fantomas.Tests/AppTests.fs b/src/Fantomas.Tests/AppTests.fs index dc8bb19c05..46350c33fe 100644 --- a/src/Fantomas.Tests/AppTests.fs +++ b/src/Fantomas.Tests/AppTests.fs @@ -110,7 +110,9 @@ module Caching = currency address sessionCachedNetworkData + ()) + () """ @@ -143,7 +145,9 @@ module Caching = if compoundBalance < 0.0m then ReportProblem looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong + ()) + () """ @@ -173,7 +177,9 @@ module Caching = | Cached (balance, time) -> if compoundBalance < 0.0m then ReportProblem compoundBalance None currency address sessionCachedNetworkData + ()) + () """ @@ -203,5 +209,6 @@ module Caching = | Cached (balance, time) -> if compoundBalance < 0.0m then ReportProblem compoundBalance ()) + () """ diff --git a/src/Fantomas.Tests/AttributeTests.fs b/src/Fantomas.Tests/AttributeTests.fs index f4e341fed0..572c3a9827 100644 --- a/src/Fantomas.Tests/AttributeTests.fs +++ b/src/Fantomas.Tests/AttributeTests.fs @@ -307,6 +307,7 @@ let main argv = printfn "Nice command-line arguments! Here's what JSON.NET has to say about them:" argv |> Array.map getJsonNetJson |> Array.iter(printfn "%s") + 0 // return an integer exit code """ @@ -484,6 +485,22 @@ open System.Runtime.InteropServices () """ +[] +let ``line comment between attributes and do expression`` () = + formatSourceString false """ +[] +[] +// barry +printfn "meh" +""" config + |> prepend newline + |> should equal """ +[] +[] +// barry +printfn "meh" +""" + [] let ``multiple attributes inside SynAttributes that exceeds max line length, 629`` () = formatSourceString false """ @@ -570,7 +587,6 @@ type RoleAdminImportController(akkaService: AkkaService) = DryRun = args.DryRun } importer.ApiMaster (configureApp giraffeApp)) .ConfigureServices(configureServices args).Build().Run() + 0 with ex -> Log.Fatal(ex, "Host terminated unexpectedly") @@ -856,7 +858,7 @@ let foo = () """ [] -let ``hash directive between attributes`` () = +let ``hash directive between attributes, no defines`` () = formatSourceStringWithDefines [] """[] #if BAR [] @@ -871,7 +873,6 @@ do () #endif [] - do () """ @@ -891,7 +892,25 @@ do () [] #endif [] +do () +""" +[] +let ``hash directive between attributes`` () = + formatSourceString false """[] +#if BAR +[] +#endif +[] +do () +""" config + |> prepend newline + |> should equal """ +[] +#if BAR +[] +#endif +[] do () """ diff --git a/src/Fantomas.Tests/ComputationExpressionTests.fs b/src/Fantomas.Tests/ComputationExpressionTests.fs index 5b36c29962..be9e6eb5dd 100644 --- a/src/Fantomas.Tests/ComputationExpressionTests.fs +++ b/src/Fantomas.Tests/ComputationExpressionTests.fs @@ -1403,7 +1403,9 @@ let initDb () = if not (File.Exists(dbFileName)) then let dbFile = File.Create(dbFileName) dbFile.Dispose() |> ignore + let createSql = readSqlFile "create" + using (connection ()) (fun conn -> task { do! conn.OpenAsync() @@ -1442,6 +1444,7 @@ let private removeSubscription (log : ILogger) (req : HttpRequest) = |> should equal """ let private removeSubscription (log : ILogger) (req : HttpRequest) = log.LogInformation("Start remove-subscription") + task { let origin = req.Headers.["Origin"].ToString() let user = Authentication.getUser log req diff --git a/src/Fantomas.Tests/ControlStructureTests.fs b/src/Fantomas.Tests/ControlStructureTests.fs index 61e43d29d7..efb2193ac1 100644 --- a/src/Fantomas.Tests/ControlStructureTests.fs +++ b/src/Fantomas.Tests/ControlStructureTests.fs @@ -56,11 +56,13 @@ let ``for loops``() = let function1 () = for i = 1 to 10 do printf "%d " i + printfn "" let function2 () = for i = 10 downto 1 do printf "%d " i + printfn "" """ @@ -85,9 +87,11 @@ open System let lookForValue value maxValue = let mutable continueLooping = true let randomNumberGenerator = new Random() + while continueLooping do let rand = randomNumberGenerator.Next(maxValue) printf "%d " rand + if rand = value then printfn "\nFound a %d!" value continueLooping <- false @@ -151,6 +155,7 @@ let ``range expressions``() = let function2() = for i in 1 .. 2 .. 10 do printf "%d " i + printfn "" function2()""" config |> prepend newline @@ -158,6 +163,7 @@ let ``range expressions``() = let function2 () = for i in 1 .. 2 .. 10 do printf "%d " i + printfn "" function2 () diff --git a/src/Fantomas.Tests/ElmishTests.fs b/src/Fantomas.Tests/ElmishTests.fs index 6939cc6fd4..bfd162314f 100644 --- a/src/Fantomas.Tests/ElmishTests.fs +++ b/src/Fantomas.Tests/ElmishTests.fs @@ -662,6 +662,7 @@ open Feliz let counter = React.functionComponent (fun () -> let (count, setCount) = React.useState (0) + Html.div [ Html.button [ prop.style [ style.marginRight 5 ] diff --git a/src/Fantomas.Tests/FSharpScriptTests.fs b/src/Fantomas.Tests/FSharpScriptTests.fs index 20146cfca6..102ccff7c8 100644 --- a/src/Fantomas.Tests/FSharpScriptTests.fs +++ b/src/Fantomas.Tests/FSharpScriptTests.fs @@ -100,6 +100,7 @@ let ``number in the filename should not end up in the module name`` () = |> String.normalizeNewLine |> should equal """let simplePatternMatch = let x = "a" + match x with | "a" -> printfn "x is a" | "b" -> printfn "x is b" diff --git a/src/Fantomas.Tests/FunctionDefinitionTests.fs b/src/Fantomas.Tests/FunctionDefinitionTests.fs index 25ef219c54..183c8850f4 100644 --- a/src/Fantomas.Tests/FunctionDefinitionTests.fs +++ b/src/Fantomas.Tests/FunctionDefinitionTests.fs @@ -126,6 +126,7 @@ let ``let bindings with return types``() = let divide x y = let stream: System.IO.FileStream = System.IO.File.Create("test.txt") let writer: System.IO.StreamWriter = new System.IO.StreamWriter(stream) + try writer.WriteLine("test1") Some(x / y) @@ -393,6 +394,7 @@ let fold (funcs : ResultFunc<'Input, 'Output, 'TError> seq) let runValidator (validator : ResultFunc<'Input, 'Output, 'TError>) input = let validatorResult = validator input + match validatorResult with | Error error -> anyErrors <- true @@ -400,6 +402,7 @@ let fold (funcs : ResultFunc<'Input, 'Output, 'TError> seq) | Ok output -> collectedOutputs <- output :: collectedOutputs funcs |> Seq.iter (fun validator -> runValidator validator input) + match anyErrors with | true -> Error collectedErrors | false -> Ok collectedOutputs @@ -646,6 +649,7 @@ let rec run : HttpResponse = logAnalyticsForRequest log req + Http.main CodeFormatter.GetVersion format diff --git a/src/Fantomas.Tests/InterfaceTests.fs b/src/Fantomas.Tests/InterfaceTests.fs index ae4b59f427..eb4a534adb 100644 --- a/src/Fantomas.Tests/InterfaceTests.fs +++ b/src/Fantomas.Tests/InterfaceTests.fs @@ -238,3 +238,17 @@ type Test = ?waitForOrderDate: bool) = "" """ + +[] +let ``interface with get/set members`` () = + formatSourceString false """ +type IMyInterface = + abstract MyProp : bool with get, set + abstract MyMethod : unit -> unit +""" config + |> prepend newline + |> should equal """ +type IMyInterface = + abstract MyProp: bool with get, set + abstract MyMethod: unit -> unit +""" diff --git a/src/Fantomas.Tests/KeepIfThenInSameLineTests.fs b/src/Fantomas.Tests/KeepIfThenInSameLineTests.fs index 4c64cdeb38..afc6683417 100644 --- a/src/Fantomas.Tests/KeepIfThenInSameLineTests.fs +++ b/src/Fantomas.Tests/KeepIfThenInSameLineTests.fs @@ -25,6 +25,7 @@ type TransferAmount(valueToSend: decimal, balanceAtTheMomentOfSending: decimal) do if balanceAtTheMomentOfSending < valueToSend then invalidArg "balanceAtTheMomentOfSending" "some very very long error message" + if valueToSend <= 0m then invalidArg "valueToSend" "Amount has to be above zero" """ @@ -49,6 +50,7 @@ type TransferAmount(valueToSend: decimal, balanceAtTheMomentOfSending: decimal) invalidArg "balanceAtTheMomentOfSending" // comment "some very very long error message" // comment + if valueToSend <= 0m then // comment invalidArg "valueToSend" "Amount has to be above zero" // comment """ diff --git a/src/Fantomas.Tests/LetBindingTests.fs b/src/Fantomas.Tests/LetBindingTests.fs index aae1bc1c2f..4ecc19b8b8 100644 --- a/src/Fantomas.Tests/LetBindingTests.fs +++ b/src/Fantomas.Tests/LetBindingTests.fs @@ -52,8 +52,11 @@ let f () = """ formatSourceString false codeSnippet config - |> should equal """let f () = + |> prepend newline + |> should equal """ +let f () = let x = 1 + if longIdentifierThatWillForceThisConstructToBeMultiline then x else x @@ -68,10 +71,14 @@ let f () = """ formatSourceString false codeSnippet config - |> should equal """let f () = + |> prepend newline + |> should equal """ +let f () = let x = 1 + (while true do () + x) """ @@ -285,15 +292,16 @@ let ``should keep space before :`` () = " [] -let ``newline trivia before simple sequence doesn't force remaining to get offset by last expression column index`` () = - // https://github.com/fsprojects/fantomas/issues/513 +let ``newline trivia before simple sequence doesn't force remaining to get offset by last expression column index, 513`` () = formatSourceString false """let a() = let q = 1 q b """ config - |> should equal """let a () = + |> prepend newline + |> should equal """ +let a () = let q = 1 q @@ -859,3 +867,81 @@ let getVersion () = new HttpResponseMessage(HttpStatusCode.OK, Content = new StringContent(version, System.Text.Encoding.UTF8, "application/text")) """ + +[] +let ``sequential after local let bindings should respect indentation, 1054`` () = + formatSourceString false " +let merge a b = + let aChunks = splitWhenHash a + let bChunks = splitWhenHash b + + if List.length aChunks <> List.length bChunks then + Dbg.print (aChunks, bChunks) + failwithf \"\"\"Fantomas is trying to format the input multiple times due to the detect of multiple defines. +There is a problem with merging all the code back togheter. Please raise an issue at https://github.com/fsprojects/fantomas/issues.\"\"\" + + List.zip aChunks bChunks + |> List.map (fun (a', b') -> + let la = lengthWithoutSpaces a' + let lb = lengthWithoutSpaces b' + if la <> lb then + if la > lb then a' else b' + else + if String.length a' < String.length b' then a' else b' + ) + + |> String.concat Environment.NewLine +" config + |> prepend newline + |> should equal " +let merge a b = + let aChunks = splitWhenHash a + let bChunks = splitWhenHash b + + if List.length aChunks <> List.length bChunks then + Dbg.print (aChunks, bChunks) + + failwithf \"\"\"Fantomas is trying to format the input multiple times due to the detect of multiple defines. +There is a problem with merging all the code back togheter. Please raise an issue at https://github.com/fsprojects/fantomas/issues.\"\"\" + + List.zip aChunks bChunks + |> List.map (fun (a', b') -> + let la = lengthWithoutSpaces a' + let lb = lengthWithoutSpaces b' + + if la <> lb then if la > lb then a' else b' + else if String.length a' < String.length b' then a' + else b') + + |> String.concat Environment.NewLine +" + +[] +let ``multiline expressions within sequential should be separated with new lines`` () = + formatSourceString false """ +let x = + if someCondition then + // + foo + else + // + bar + while someCondition do + printfn "meh" + () +""" config + |> prepend newline + |> should equal """ +let x = + if someCondition then + // + foo + else + // + bar + + while someCondition do + printfn "meh" + + () +""" diff --git a/src/Fantomas.Tests/LongIdentWithDotsTests.fs b/src/Fantomas.Tests/LongIdentWithDotsTests.fs index f08e176f42..3e35c6927b 100644 --- a/src/Fantomas.Tests/LongIdentWithDotsTests.fs +++ b/src/Fantomas.Tests/LongIdentWithDotsTests.fs @@ -192,11 +192,13 @@ module Client = .Login(fun e -> passwordValid := not (String.IsNullOrWhiteSpace e.Vars.Password.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) + e.Event.PreventDefault()).Bind() """ diff --git a/src/Fantomas.Tests/PatternMatchingTests.fs b/src/Fantomas.Tests/PatternMatchingTests.fs index 26024ff60c..e1d700cef5 100644 --- a/src/Fantomas.Tests/PatternMatchingTests.fs +++ b/src/Fantomas.Tests/PatternMatchingTests.fs @@ -226,6 +226,7 @@ let UNIFY_ACCEPT_TAC mvs th (asl, w) = |> should equal """ let UNIFY_ACCEPT_TAC mvs th (asl, w) = let insts = term_unify mvs (concl th) w + ([], insts), [], let th' = INSTANTIATE insts th diff --git a/src/Fantomas.Tests/QueueTests.fs b/src/Fantomas.Tests/QueueTests.fs index 9b212a03a5..2cb8920c44 100644 --- a/src/Fantomas.Tests/QueueTests.fs +++ b/src/Fantomas.Tests/QueueTests.fs @@ -56,7 +56,7 @@ let ``Queue.skipExists``() = let f = id n <= List.sumBy List.length xss ==> lazy - (let result = Queue.ofLists xss |> Queue.skipExists n f + (let result = Queue.ofLists xss |> Queue.skipExists n f (fun _ -> false) let expected = xss |> List.collect id |> Seq.skip n |> Seq.exists f result |> should equal expected) ) diff --git a/src/Fantomas.Tests/RecordTests.fs b/src/Fantomas.Tests/RecordTests.fs index f35ecf9098..5dceeff93e 100644 --- a/src/Fantomas.Tests/RecordTests.fs +++ b/src/Fantomas.Tests/RecordTests.fs @@ -501,6 +501,7 @@ I wanted to know why you created Fable. Did you always plan to use F#? Or were y 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 830e0e3bea..9817b510d2 100644 --- a/src/Fantomas.Tests/TypeDeclarationTests.fs +++ b/src/Fantomas.Tests/TypeDeclarationTests.fs @@ -616,6 +616,7 @@ type BlobHelper(Account: CloudStorageAccount) = configSettingPublisher.Invoke(connectionString) |> ignore) + BlobHelper(CloudStorageAccount.FromConfigurationSetting(configurationSettingName)) """ diff --git a/src/Fantomas.Tests/UnionTests.fs b/src/Fantomas.Tests/UnionTests.fs index a6517ed4dc..4ff85f3af4 100644 --- a/src/Fantomas.Tests/UnionTests.fs +++ b/src/Fantomas.Tests/UnionTests.fs @@ -148,6 +148,7 @@ type TestUnion = Test of A: int * B: int [] let main argv = let d = Test(B = 1, A = 2) + match d with | Test (A = a; B = b) -> a + b | _ -> 0 diff --git a/src/Fantomas/AstTransformer.fs b/src/Fantomas/AstTransformer.fs index ca0323c48e..a676dbec2d 100644 --- a/src/Fantomas/AstTransformer.fs +++ b/src/Fantomas/AstTransformer.fs @@ -192,7 +192,7 @@ module private Ast = FsAstNode = synExpr Childs = [ yield! exprs |> List.map visitSynExpr ] } | SynExpr.ArrayOrList (isList, exprs, range) -> - { Type = SynExpr_StructTuple + { Type = SynExpr_ArrayOrList Range = r range Properties = p [ "isList" ==> isList ] FsAstNode = synExpr diff --git a/src/Fantomas/CodePrinter.fs b/src/Fantomas/CodePrinter.fs index 859a928084..01f3a3bb8b 100644 --- a/src/Fantomas/CodePrinter.fs +++ b/src/Fantomas/CodePrinter.fs @@ -181,9 +181,9 @@ and genSigModuleOrNamespace astContext (SigModuleOrNamespace(ats, px, ao, s, mds | Some mdl -> match mdl with | SynModuleSigDecl.Types _ -> - sepNlnConsideringTriviaContentBeforeFor SynModuleSigDecl_Types mdl.Range + sepNlnConsideringTriviaContentBeforeForMainNode SynModuleSigDecl_Types mdl.Range | SynModuleSigDecl.Val _ -> - sepNlnConsideringTriviaContentBeforeFor ValSpfn_ mdl.Range + sepNlnConsideringTriviaContentBeforeForMainNode ValSpfn_ mdl.Range | _ -> sepNone +> sepNln @@ -208,67 +208,48 @@ and genSigModuleOrNamespace astContext (SigModuleOrNamespace(ats, px, ao, s, mds +> leaveNodeFor SynModuleOrNamespaceSig_NamedModule range and genModuleDeclList astContext e = - match e with - | OpenL(xs, ys) -> - match ys with - | [] -> col sepNln xs (fun mdl -> enterNodeFor SynModuleDecl_Open mdl.Range +> genModuleDecl astContext mdl) - | _ -> - let sepModuleDecl = - match List.tryHead ys with - | Some _ -> sepNln - | None -> rep 2 sepNln + let rec collectItems e = + match e with + | [] -> [] + | OpenL (xs, ys) -> + let expr = col sepNln xs (genModuleDecl astContext) + + let r = + List.head xs + |> fun mdl -> mdl.Range + // SynModuleDecl.Open cannot have attributes + let sepNln = + sepNlnConsideringTriviaContentBeforeForMainNode SynModuleDecl_Open r + + [ expr, sepNln, r ] @ collectItems ys + | AttributesL (xs, y :: rest) -> + let attrs = + getRangesFromAttributesFromModuleDeclaration y - (col sepNln xs (fun mdl -> enterNodeFor SynModuleDecl_Open mdl.Range +> genModuleDecl astContext mdl) +> sepModuleDecl +> genModuleDeclList astContext ys) - | m::rest -> - let attrs = getRangesFromAttributesFromModuleDeclaration m + let expr = + col sepNln xs (genModuleDecl astContext) + +> sepNlnConsideringTriviaContentBeforeWithAttributesFor (synModuleDeclToFsAstType y) y.Range attrs + +> genModuleDecl astContext y - let newlineAfterMultiline ctx = - let sepNlnConsideringTriviaContentBeforeWithAttributes node (rm:range) attrs = - match node with - | SynModuleDecl.DoExpr _ -> sepNlnConsideringTriviaContentBeforeWithAttributesFor SynModuleDecl_DoExpr rm attrs - | SynModuleDecl.Types _ -> sepNlnConsideringTriviaContentBeforeWithAttributesFor SynModuleDecl_Types rm attrs - | SynModuleDecl.NestedModule _ -> sepNlnConsideringTriviaContentBeforeWithAttributesFor SynModuleDecl_NestedModule rm attrs - | SynModuleDecl.Let _ -> sepNlnConsideringTriviaContentBeforeWithAttributesFor SynModuleDecl_Let rm attrs - | SynModuleDecl.Open _ -> sepNlnConsideringTriviaContentBeforeWithAttributesFor SynModuleDecl_Open rm attrs - | _ -> sepNln + let r = + List.head xs + |> fun mdl -> mdl.Range - let expr = - match List.tryHead rest with - | Some (SynModuleDecl.DoExpr(_, SynExpr.Const(SynConst.Unit,_),rm) as node) -> - sepNlnConsideringTriviaContentBeforeWithAttributes node rm attrs - | Some mdl -> - let attrs = getRangesFromAttributesFromModuleDeclaration mdl - sepNln +> sepNlnConsideringTriviaContentBeforeWithAttributes mdl mdl.Range attrs - | None -> sepNone - expr ctx + let sepNln = + sepNlnConsideringTriviaContentBeforeForMainNode SynModuleDecl_Attributes r - let mainNodeName = - match m with - | SynModuleDecl.Let _ -> Some SynModuleDecl_Let - | SynModuleDecl.Exception _ -> Some SynModuleDecl_Exception - | SynModuleDecl.Types _ -> Some SynModuleDecl_Types - | SynModuleDecl.NestedModule _ -> Some SynModuleDecl_NestedModule - | SynModuleDecl.DoExpr _ -> Some SynModuleDecl_DoExpr - | SynModuleDecl.Open _ -> Some SynModuleDecl_Open - | SynModuleDecl.Attributes _ -> Some SynModuleDecl_Attributes - | SynModuleDecl.HashDirective _ -> Some SynModuleDecl_HashDirective - | _ -> None + [ expr, sepNln, r ] @ collectItems rest + | m :: rest -> + let attrs = + getRangesFromAttributesFromModuleDeclaration m - (match mainNodeName with - | Some mn -> enterNodeFor mn m.Range - | None -> id) - +> leadingExpressionIsMultiline - (expressionFitsOnRestOfLine - (genModuleDecl astContext m) - (match mainNodeName with - | Some mn -> sepNlnBeforeMultilineConstruct mn m.Range attrs - | None -> sepNln - +> genModuleDecl astContext m +> newlineAfterMultiline)) - (fun multiline -> onlyIf (not multiline && List.isNotEmpty rest) sepNln) + let sepNln = + sepNlnConsideringTriviaContentBeforeWithAttributesFor (synModuleDeclToFsAstType m) m.Range attrs - +> genModuleDeclList astContext rest + let expr = genModuleDecl astContext m + (expr, sepNln, m.Range) :: (collectItems rest) - | [] -> sepNone + collectItems e |> colWithNlnWhenItemIsMultiline and genSigModuleDeclList astContext node = match node with @@ -321,7 +302,7 @@ and genSigModuleDeclList astContext node = | SynModuleSigDecl.NamespaceFragment _ -> SynModuleSigDecl_NamespaceFragment | SynModuleSigDecl.ModuleAbbrev _ -> SynModuleSigDecl_ModuleAbbrev - sepNln +> sepNlnConsideringTriviaContentBeforeFor mainNode y.Range + sepNln +> sepNlnConsideringTriviaContentBeforeForMainNode mainNode y.Range | None -> rep 2 sepNln col (rep 2 sepNln) xs (genSigModuleDecl astContext) @@ -343,7 +324,7 @@ and genModuleDecl astContext (node: SynModuleDecl) = ifElse prevContentAfterPresent sepNone - (sepNlnConsideringTriviaContentBeforeFor SynModuleDecl_Attributes a.Range) + (sepNlnConsideringTriviaContentBeforeForMainNode SynModuleDecl_Attributes a.Range) +> ((col sepNln a.Attributes (genAttribute astContext)) |> genTriviaFor SynAttributeList_ a.Range) let hasContentAfter = @@ -374,14 +355,14 @@ and genModuleDecl astContext (node: SynModuleDecl) = match List.tryHead bs with | Some b' -> let r = b'.RangeOfBindingSansRhs - sepNln +> sepNlnConsideringTriviaContentBeforeFor Binding_ r + sepNln +> sepNlnConsideringTriviaContentBeforeForMainNode Binding_ r | None -> id genLetBinding { astContext with IsFirstChild = true } "let rec " b +> sepBAndBs +> colEx (fun (b': SynBinding) -> let r = b'.RangeOfBindingSansRhs - sepNln +> sepNlnConsideringTriviaContentBeforeFor Binding_ r + sepNln +> sepNlnConsideringTriviaContentBeforeForMainNode Binding_ r ) bs (fun andBinding -> enterNodeFor Binding_ andBinding.RangeOfBindingSansRhs +> genLetBinding { astContext with IsFirstChild = false } "and " andBinding) @@ -404,24 +385,15 @@ and genModuleDecl astContext (node: SynModuleDecl) = | Types(t::ts) -> let sepTs = match List.tryHead ts with - | Some t -> sepNln +> sepNlnConsideringTriviaContentBeforeFor TypeDefn_ t.Range + | Some t -> sepNln +> sepNlnConsideringTriviaContentBeforeForMainNode TypeDefn_ t.Range | None -> rep 2 sepNln genTypeDefn { astContext with IsFirstChild = true } t +> colPreEx sepTs (fun (ty: SynTypeDefn) -> - sepNln +> sepNlnConsideringTriviaContentBeforeFor TypeDefn_ ty.Range) ts (genTypeDefn { astContext with IsFirstChild = false }) + sepNln +> sepNlnConsideringTriviaContentBeforeForMainNode TypeDefn_ ty.Range) ts (genTypeDefn { astContext with IsFirstChild = false }) | md -> failwithf "Unexpected module declaration: %O" md - +> fun ctx -> - match node with - | SynModuleDecl.Exception _ -> leaveNodeFor SynModuleDecl_Exception node.Range ctx - | SynModuleDecl.Let _ -> leaveNodeFor SynModuleDecl_Let node.Range ctx - | SynModuleDecl.Types _ -> leaveNodeFor SynModuleDecl_Types node.Range ctx - | SynModuleDecl.NestedModule _ -> leaveNodeFor SynModuleDecl_NestedModule node.Range ctx - | SynModuleDecl.DoExpr _ -> leaveNodeFor SynModuleDecl_DoExpr node.Range ctx - | SynModuleDecl.Open _ -> leaveNodeFor SynModuleDecl_Open node.Range ctx - | SynModuleDecl.Attributes _ -> leaveNodeFor SynModuleDecl_Attributes node.Range ctx - | _ -> ctx + |> genTriviaFor (synModuleDeclToFsAstType node) node.Range and genSigModuleDecl astContext node = match node with @@ -688,31 +660,18 @@ and genPropertyWithGetSet astContext (b1, b2) rangeOfMember = +> unindent | _ -> sepNone -/// Each member is separated by a new line. and genMemberBindingList astContext node = - let newlineAfterMultiline rest ctx = - let expr = - match List.tryHead rest with - | Some _ -> sepNln - | None -> sepNone - expr ctx + let rec collectItems (node: SynBinding list) = + match node with + | [] -> [] + | mb::rest -> + let expr = genMemberBinding astContext mb + let r = mb.RangeOfBindingSansRhs + let sepNln = sepNlnConsideringTriviaContentBeforeForMainNode Binding_ r + (expr, sepNln, r)::(collectItems rest) - match node with - | PropertyWithGetSet(gs, rest) -> - leadingExpressionIsMultiline - (expressionFitsOnRestOfLine - (genPropertyWithGetSet astContext gs None) - (sepNln +> genPropertyWithGetSet astContext gs None +> newlineAfterMultiline rest)) - (fun multiline -> onlyIf (not multiline && List.isNotEmpty rest) sepNln) - +> genMemberBindingList astContext rest - | mb::rest -> - leadingExpressionIsMultiline - (expressionFitsOnRestOfLine - (genMemberBinding astContext mb) - (genMemberBinding astContext mb +> newlineAfterMultiline rest)) - (fun multiline -> onlyIf (not multiline && List.isNotEmpty rest) sepNln) - +> genMemberBindingList astContext rest - | _ -> sepNone + collectItems node + |> colWithNlnWhenItemIsMultiline and genMemberBinding astContext b = match b with @@ -1300,11 +1259,11 @@ and genExpr astContext synExpr = | AndBangStatement (_, _, r) -> r | OtherStatement expr -> expr.Range - let getKey ces = + let getSepNln ces r = match ces with - | LetOrUseStatement _ -> Binding_ |> Choice1Of2 - | LetOrUseBangStatement _ -> SynExpr_LetOrUseBang |> Choice1Of2 - | AndBangStatement _ -> AND_BANG |> Choice2Of2 + | LetOrUseStatement _ -> sepNlnConsideringTriviaContentBeforeForMainNode Binding_ r + | LetOrUseBangStatement _ -> sepNlnConsideringTriviaContentBeforeForMainNode SynExpr_LetOrUseBang r + | AndBangStatement _ -> sepNlnConsideringTriviaContentBeforeForToken AND_BANG r | OtherStatement e -> match e with | SynExpr.App _ -> SynExpr_App @@ -1315,10 +1274,14 @@ and genExpr astContext synExpr = | SynExpr.Do _ -> SynExpr_Do | SynExpr.DoBang _ -> SynExpr_DoBang | _ -> SynExpr_Const - |> Choice1Of2 + |> fun mn -> sepNlnConsideringTriviaContentBeforeForMainNode mn r statements - |> List.map (fun ces -> genCompExprStatement astContext ces, getKey ces, getRangeOfCompExprStatement ces) + |> List.map (fun ces -> + let expr = genCompExprStatement astContext ces + let r = getRangeOfCompExprStatement ces + let sepNln = getSepNln ces r + expr, sepNln, r) |> colWithNlnWhenItemIsMultiline | ArrayOrListOfSeqExpr(isArray, e) as aNode -> @@ -1639,29 +1602,41 @@ and genExpr astContext synExpr = not (isFromAst ctx) && p.Range.EndLine = e.Range.StartLine && not (futureNlnCheck (genExpr astContext e) ctx) | _ -> false - let sepNlnBeforeExpr = - match e with - | SynExpr.Sequential(_,_, (SynExpr.App _ as app),_,_) -> sepNlnConsideringTriviaContentBeforeFor SynExpr_App app.Range - | SynExpr.Sequential(_,_, (SynExpr.IfThenElse _ as ite),_,_) -> sepNlnConsideringTriviaContentBeforeFor SynExpr_IfThenElse ite.Range - | SynExpr.YieldOrReturn _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_YieldOrReturn e.Range - | SynExpr.IfThenElse _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_IfThenElse e.Range - | SynExpr.LetOrUseBang _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_LetOrUseBang e.Range - | SynExpr.Const _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_Const e.Range - | SynExpr.Lambda _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_Lambda e.Range - | SynExpr.Ident _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_Ident e.Range - | SynExpr.App _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_App e.Range - | SynExpr.Match _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_Match e.Range - | SynExpr.Record _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_Record e.Range - | SynExpr.Tuple _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_Tuple e.Range - | SynExpr.DoBang _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_DoBang e.Range - | SynExpr.Paren _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_Paren e.Range - | SynExpr.AnonRecd _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_AnonRecd e.Range - | SynExpr.ArrayOrListOfSeqExpr _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_ArrayOrListOfSeqExpr e.Range - | SynExpr.LongIdentSet _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_LongIdentSet e.Range - | SynExpr.New _ -> sepNlnConsideringTriviaContentBeforeFor SynExpr_New e.Range - | _ -> sepNln + fun ctx -> + if isInSameLine ctx then + atCurrentColumn + (col sepNone bs (fun (p,x) -> genLetBinding { astContext with IsFirstChild = p <> "and" } p x) + -- " in " + +> genExpr astContext e) + ctx + else + let letBindings (bs:(string * SynBinding) list) = + bs + |> List.map (fun (p,x) -> + let expr = + enterNodeFor Binding_ x.RangeOfBindingSansRhs + +> genLetBinding { astContext with IsFirstChild = p <> "and" } p x + + let range = x.RangeOfBindingSansRhs + let sepNln = sepNlnConsideringTriviaContentBeforeForMainNode Binding_ range + expr, sepNln, range) + + let sepNlnForNonSequential e r = + sepNlnConsideringTriviaContentBeforeForMainNode (synExprToFsAstType e) r + + let rec synExpr e = + match e with + | LetOrUses(bs, e) -> + letBindings bs @ synExpr e + | Sequentials(s) -> + s + |> List.collect synExpr + | _ -> + let r = e.Range + [ genExpr astContext e, sepNlnForNonSequential e r, r ] - atCurrentColumn (genLetOrUseList astContext bs +> ifElseCtx isInSameLine (!- " in ") sepNlnBeforeExpr +> genExpr astContext e) + let items = letBindings bs @ synExpr e + atCurrentColumn(colWithNlnWhenItemIsMultiline items) ctx // Could customize a bit if e is single line | TryWith(e, cs) -> @@ -1678,15 +1653,17 @@ and genExpr astContext synExpr = atCurrentColumn (kw TRY !-"try " +> indent +> sepNln +> genExpr astContext e1 +> unindent +> kw FINALLY !+~"finally" +> indent +> sepNln +> genExpr astContext e2 +> unindent) - | SequentialSimple es | Sequentials es -> - // This is one situation where the trivia needs to printed before atCurrentColumn due to compiler restriction (last tested FCS 32) - // If the trivia would be printed in a AtCurrentColumn block that code would be started too far off, - // and thus, engender compile errors. - // See : - // * https://github.com/fsprojects/fantomas/issues/478 - // * https://github.com/fsprojects/fantomas/issues/513 - firstNewlineOrComment es - +> atCurrentColumn (colEx (fun (e:SynExpr) -> sepConsideringTriviaContentBefore sepSemiNln (Choice1Of2 SynExpr_IfThenElse) e.Range ) es (genExpr astContext)) + | SequentialSimple es + | Sequentials es -> + let items = + es + |> List.map (fun e -> + let expr = genExpr astContext e + let r = e.Range + let sepNln = sepConsideringTriviaContentBeforeForMainNode sepSemiNln (synExprToFsAstType e) r + expr, sepNln, r) + + atCurrentColumn (colWithNlnWhenItemIsMultiline items) | IfThenElse(e1, e2, None, mIfToThen) -> fun (ctx:Context) -> @@ -1937,14 +1914,14 @@ and genExpr astContext synExpr = genIf synExpr.Range +> genExpr astContext e1 +> sepSpace +> genThen synExpr.Range +> genExpr astContext e2 +> sepNln - +> colPost sepNln sepNln elfis genElifOneliner + +> col sepNln elfis genElifOneliner +> opt id enOpt (fun e4 -> let correctedElseRange = match List.tryLast elfis with | Some (_, te, _) -> mkRange "correctedElseRange" te.Range.End synExpr.Range.End | None -> synExpr.Range - genElse correctedElseRange +> genExpr astContext e4) + sepNln +> genElse correctedElseRange +> genExpr astContext e4) else if hasCommentAfterIfBranchExpr && not hasElfis then // f.ex @@ -2264,31 +2241,6 @@ and genMultiLineArrayOrListAlignBrackets (isArray:bool) xs alNode astContext = expr ctx -and genLetOrUseList astContext expr = - match expr with - | (p, x)::rest -> - let attrs = getRangesFromAttributesFromSynBinding x - - let newlineAfter ctx = - match List.tryHead rest with - | Some (_,lb) -> sepNlnConsideringTriviaContentBeforeFor Binding_ lb.RangeOfBindingSansRhs ctx // sepNlnConsideringTriviaContentBefore lb.RangeOfBindingSansRhs ctx - | None -> sepNln ctx - - // Call trivia manually before genLetBinding - // Otherwise the expression might become multiline because of comments or defines above the let binding - enterNodeFor Binding_ x.RangeOfBindingSansRhs - +> (leadingExpressionIsMultiline - (expressionFitsOnRestOfLine - (genLetBinding { astContext with IsFirstChild = p <> "and" } p x) - (sepNlnBeforeMultilineConstruct Binding_ x.RangeOfBindingSansRhs attrs - +> genLetBinding { astContext with IsFirstChild = p <> "and" } p x - +> onlyIf (List.isNotEmpty rest) sepNln - +> newlineAfter)) - (fun multiline -> onlyIf (not multiline && List.isNotEmpty rest) newlineAfter)) - +> genLetOrUseList astContext rest - - | _ -> sepNone - and genInfixAppsShort astContext synExprs = col sepSpace synExprs (fun (s, opE, e) -> genInfixApp s opE e astContext) @@ -2389,6 +2341,7 @@ and genTypeDefn astContext (TypeDef(ats, px, ao, tds, tcs, tdr, ms, s, preferPos +> indent +> sepNln +> genTriviaFor SynTypeDefnSimpleRepr_Enum tdr.Range (col sepNln ecs (genEnumCase { astContext with HasVerticalBar = true }) + +> onlyIf (List.isNotEmpty ms) sepNln +> sepNlnBetweenTypeAndMembers ms +> genMemberDefnList { astContext with InterfaceRange = None } ms // Add newline after un-indent to be spacing-correct @@ -2421,8 +2374,10 @@ and genTypeDefn astContext (TypeDef(ats, px, ao, tds, tcs, tdr, ms, s, preferPos +> col sepNln xs (genUnionCase { astContext with HasVerticalBar = true })) <| ctx - typeName +> sepEq + typeName + +> sepEq +> unionCases + +> onlyIf (List.isNotEmpty ms) sepNln +> sepNlnBetweenTypeAndMembers ms +> genMemberDefnList { astContext with InterfaceRange = None } ms +> unindent @@ -2469,6 +2424,7 @@ and genTypeDefn astContext (TypeDef(ats, px, ao, tds, tcs, tdr, ms, s, preferPos ifElse (List.isEmpty ms) (!- "") (indent ++ "with" +> indent + +> sepNln +> sepNlnBetweenTypeAndMembers ms +> genMemberDefnList { astContext with InterfaceRange = None } ms +> unindent +> unindent) @@ -2496,9 +2452,11 @@ and genTypeDefn astContext (TypeDef(ats, px, ao, tds, tcs, tdr, ms, s, preferPos sepEqFixed ctx else sepEq ctx) - +> indent +> sepNln - +> genTypeDefKind tdk +> indent + +> sepNln + +> genTypeDefKind tdk + +> indent + +> onlyIf (List.isNotEmpty others) sepNln +> sepNlnBetweenTypeAndMembers ms +> genMemberDefnList astContext others +> unindent @@ -2510,10 +2468,14 @@ and genTypeDefn astContext (TypeDef(ats, px, ao, tds, tcs, tdr, ms, s, preferPos match ms with | [] -> sepNone | _ -> sepNln - typeName +> opt sepNone impCtor (genMemberDefn astContext) +> sepEq - +> indent +> sepNln + typeName + +> opt sepNone impCtor (genMemberDefn astContext) + +> sepEq + +> indent + +> sepNln +> genTypeDefKind tdk +> indent + +> sepNln +> genMemberDefnList astContext others +> unindent ++ "end" @@ -2525,6 +2487,7 @@ and genTypeDefn astContext (TypeDef(ats, px, ao, tds, tcs, tdr, ms, s, preferPos | ObjectModel(TCSimple TCAugmentation, _, _) -> typeName -- " with" +> indent // Remember that we use MemberDefn of parent node + +> sepNln +> sepNlnBetweenTypeAndMembers ms +> genMemberDefnList { astContext with InterfaceRange = None } ms +> unindent @@ -2533,11 +2496,16 @@ and genTypeDefn astContext (TypeDef(ats, px, ao, tds, tcs, tdr, ms, s, preferPos typeName +> sepEq +> sepSpace +> !- "delegate of " +> genTypeList astContext ts | ObjectModel(TCSimple TCUnspecified, MemberDefnList(impCtor, others), _) when not(List.isEmpty ms) -> - typeName +> opt sepNone impCtor (genMemberDefn { astContext with InterfaceRange = None }) +> sepEq +> indent + typeName + +> opt sepNone impCtor (genMemberDefn { astContext with InterfaceRange = None }) + +> sepEq + +> indent + +> sepNln +> genMemberDefnList { astContext with InterfaceRange = None } others +> sepNln -- "with" +> indent + +> sepNln +> genMemberDefnList { astContext with InterfaceRange = None } ms +> unindent +> unindent @@ -2547,6 +2515,7 @@ and genTypeDefn astContext (TypeDef(ats, px, ao, tds, tcs, tdr, ms, s, preferPos +> opt sepNone impCtor (fun mdf -> sepSpaceBeforeClassConstructor +> genMemberDefn { astContext with InterfaceRange = None } mdf) +> sepEq +> indent + +> sepNln +> genMemberDefnList { astContext with InterfaceRange = None } others +> unindent @@ -2561,7 +2530,9 @@ and genMultilineSimpleRecordTypeDefn tdr ms ao' fs astContext = +> genTriviaFor SynTypeDefnSimpleRepr_Record tdr.Range (opt sepSpace ao' genAccess +> sepOpenS - +> atCurrentColumn (leaveLeftBrace tdr.Range +> col sepSemiNln fs (genField astContext "")) +> sepCloseS + +> atCurrentColumn (leaveLeftBrace tdr.Range +> col sepSemiNln fs (genField astContext "")) + +> sepCloseS + +> onlyIf (List.isNotEmpty ms) sepNln +> sepNlnBetweenTypeAndMembers ms +> genMemberDefnList { astContext with InterfaceRange = None } ms +> unindent) @@ -2575,6 +2546,7 @@ and genMultilineSimpleRecordTypeDefnAlignBrackets tdr ms ao' fs astContext = +> sepOpenSFixed +> indent +> sepNln +> atCurrentColumn (leaveLeftBrace tdr.Range +> col sepSemiNln fs (genField astContext "")) +> unindent +> sepNln +> sepCloseSFixed + +> onlyIf (List.isNotEmpty ms) sepNln +> sepNlnBetweenTypeAndMembers ms +> genMemberDefnList { astContext with InterfaceRange = None } ms +> onlyIf (Option.isSome ao') unindent @@ -2784,7 +2756,7 @@ and genExceptionBody astContext ats px ao uc = and genException astContext (ExceptionDef(ats, px, ao, uc, ms) as node) = genExceptionBody astContext ats px ao uc +> ifElse ms.IsEmpty sepNone - (!- " with" +> indent +> genMemberDefnList { astContext with InterfaceRange = None } ms +> unindent) + (!- " with" +> indent +> sepNln +> genMemberDefnList { astContext with InterfaceRange = None } ms +> unindent) |> genTriviaFor SynExceptionDefn_ node.Range and genSigException astContext (SigExceptionDef(ats, px, ao, uc, ms)) = @@ -3008,53 +2980,28 @@ and genClause astContext hasBar (Clause(p, e, eo)) = /// Each multiline member definition has a pre and post new line. and genMemberDefnList astContext nodes = - match nodes with - | MDOpenL(xs, ys) -> - fun ctx -> - match ys with - | [] -> col sepNln xs (genMemberDefn astContext) ctx - | _ -> (col sepNln xs (genMemberDefn astContext) +> rep 2 sepNln +> genMemberDefnList astContext ys) ctx - - | PropertyWithGetSetMemberDefn(gs, rest) -> - let rangeOfFirstMember = List.head nodes |> fun m -> m.Range - let m = fst gs - let attrs = getRangesFromAttributesFromSynBinding (fst gs) - - sepNlnConsideringTriviaContentBeforeWithAttributesFor SynMemberDefn_Member rangeOfFirstMember attrs - +> enterNodeFor SynMemberDefn_Member rangeOfFirstMember - +> (expressionFitsOnRestOfLine - (genPropertyWithGetSet astContext gs (Some rangeOfFirstMember)) - (sepNlnBeforeMultilineConstruct SynMemberDefn_Member m.RangeOfBindingSansRhs attrs - +> genPropertyWithGetSet astContext gs (Some rangeOfFirstMember) - +> onlyIf (List.isNotEmpty rest) sepNln)) - +> genMemberDefnList ({ astContext with IsFirstChild = false }) rest - - | m::rest -> - let attrs = getRangesFromAttributesFromSynMemberDefinition m - - let mainNode = - match m with - | SynMemberDefn.Member _ -> Some SynMemberDefn_Member - | SynMemberDefn.AbstractSlot _ -> Some SynMemberDefn_AbstractSlot - | SynMemberDefn.LetBindings _ -> Some SynMemberDefn_LetBindings - | SynMemberDefn.ValField _ -> Some SynMemberDefn_ValField - | SynMemberDefn.Interface _ -> Some SynMemberDefn_Interface - | _ -> None + let rec collectItems nodes = + match nodes with + | [] -> [] + | PropertyWithGetSetMemberDefn(gs, rest) -> + let attrs = getRangesFromAttributesFromSynBinding (fst gs) + let rangeOfFirstMember = List.head nodes |> fun m -> m.Range - (match mainNode with - | Some mn -> - (sepNlnConsideringTriviaContentBeforeWithAttributesFor mn m.Range attrs - +> enterNodeFor mn m.Range) - | None -> sepNln) - +> (expressionFitsOnRestOfLine - (genMemberDefn astContext m) - (onlyIfNot astContext.IsFirstChild (match mainNode with | Some mn -> sepNlnBeforeMultilineConstruct mn m.Range attrs | None -> sepNln) - +> genMemberDefn astContext m - +> onlyIf (List.isNotEmpty rest) sepNln)) + let expr = + enterNodeFor SynMemberDefn_Member rangeOfFirstMember + +> genPropertyWithGetSet astContext gs (Some rangeOfFirstMember) - +> genMemberDefnList ({ astContext with IsFirstChild = false }) rest + let sepNln = sepNlnConsideringTriviaContentBeforeWithAttributesFor SynMemberDefn_Member rangeOfFirstMember attrs + (expr, sepNln, rangeOfFirstMember)::(collectItems rest) - | _ -> sepNone + | m::rest -> + let attrs = getRangesFromAttributesFromSynMemberDefinition m + let expr = genMemberDefn astContext m + let sepNln = sepNlnConsideringTriviaContentBeforeWithAttributesFor (synMemberDefnToFsAstType m) m.Range attrs + (expr, sepNln, m.Range)::(collectItems rest) + + collectItems nodes + |> colWithNlnWhenItemIsMultiline and genMemberDefn astContext node = match node with @@ -3124,7 +3071,7 @@ and genMemberDefn astContext node = | MDInterface(t, mdo, range) -> !- "interface " +> genType astContext false t +> opt sepNone mdo - (fun mds -> !- " with" +> indent +> genMemberDefnList { astContext with InterfaceRange = Some range } mds +> unindent) + (fun mds -> !- " with" +> indent +> sepNln +> genMemberDefnList { astContext with InterfaceRange = Some range } mds +> unindent) | MDAutoProperty(ats, px, ao, mk, e, s, _isStatic, typeOpt, memberKindToMemberFlags) -> let isFunctionProperty = @@ -3156,7 +3103,7 @@ and genMemberDefn astContext node = +> genConstraints astContext t | md -> failwithf "Unexpected member definition: %O" md - +> leaveNodeFor SynMemberDefn_Member node.Range + |> genTriviaFor (synMemberDefnToFsAstType node) node.Range and genPropertyKind useSyntacticSugar node = match node with diff --git a/src/Fantomas/Context.fs b/src/Fantomas/Context.fs index 4504091bc2..c293bc20be 100644 --- a/src/Fantomas/Context.fs +++ b/src/Fantomas/Context.fs @@ -246,6 +246,20 @@ let internal lastWriteEventIsNewline ctx = |> Option.map (function | WriteLine -> true | _ -> false) |> Option.defaultValue false +let private (|CommentOrDefineEvent|_|) we = + match we with + | Write (w) when (String.startsWithOrdinal "//" w) -> + Some we + | Write (w) when (String.startsWithOrdinal "#if" w) -> + Some we + | Write (w) when (String.startsWithOrdinal "#else" w) -> + Some we + | Write (w) when (String.startsWithOrdinal "#endif" w) -> + Some we + | Write (w) when (String.startsWithOrdinal "(*" w) -> + Some we + | _ -> None + /// Validate if there is a complete blank line between the last write event and the last event let internal newlineBetweenLastWriteEvent ctx = ctx.WriterEvents @@ -604,10 +618,27 @@ let internal leadingExpressionLong threshold leadingExpression continuationExpre let (lineCountAfter, columnAfter) = List.length contextAfterLeading.WriterModel.Lines, contextAfterLeading.WriterModel.Column continuationExpression (lineCountAfter > lineCountBefore || (columnAfter - columnBefore > threshold)) contextAfterLeading +/// A leading expression is not consider multiline if it has a comment before it. +/// For example +/// let a = 7 +/// // foo +/// let b = 8 +/// let c = 9 +/// The second binding b is not consider multiline. let internal leadingExpressionIsMultiline leadingExpression continuationExpression (ctx: Context) = let eventCountBeforeExpression = Queue.length ctx.WriterEvents let contextAfterLeading = leadingExpression ctx - let hasWriteLineEventsAfterExpression = contextAfterLeading.WriterEvents |> Queue.skipExists eventCountBeforeExpression (function | WriteLine _ -> true | _ -> false) + + let hasWriteLineEventsAfterExpression = + contextAfterLeading.WriterEvents + |> Queue.skipExists eventCountBeforeExpression (function + | WriteLine _ -> true + | _ -> false) (fun e -> + match e with + | [| CommentOrDefineEvent(_) |] + | [| WriteLine |] + | [| Write "" |] -> true + | _ -> false) continuationExpression hasWriteLineEventsAfterExpression contextAfterLeading @@ -890,14 +921,13 @@ let internal hasPrintableContent (trivia: TriviaContent list) = trivia |> List.exists (fun tn -> match tn with - | Comment(_) -> true - | Newline -> true + | Comment _ + | Newline + | Directive _ -> true | _ -> false) let private sepConsideringTriviaContentBeforeBy (findNode: Context -> range -> TriviaNode option) (sepF: Context -> Context) (range: range) (ctx: Context) = match findNode ctx range with - | Some({ ContentBefore = (Comment(BlockComment(_,false,_)))::_ }) -> - sepF ctx | Some({ ContentBefore = contentBefore }) when (hasPrintableContent contentBefore) -> ctx | _ -> sepF ctx @@ -914,6 +944,14 @@ let internal sepConsideringTriviaContentBefore sepF (key: Choice) (range:range) = sepConsideringTriviaContentBefore sepNln key range -let internal sepNlnConsideringTriviaContentBeforeFor (mainNode: FsAstType) (range:range) = +let internal sepNlnConsideringTriviaContentBeforeForToken (fsTokenKey: FsTokenType) (range:range) = + sepConsideringTriviaContentBeforeForToken sepNln fsTokenKey range + +let internal sepNlnConsideringTriviaContentBeforeForMainNode (mainNode: FsAstType) (range:range) = sepConsideringTriviaContentBeforeForMainNode sepNln mainNode range let internal sepNlnConsideringTriviaContentBeforeWithAttributesFor @@ -972,8 +1013,8 @@ let internal sepNlnForEmptyNamespace (namespaceRange:range) ctx = let internal sepNlnTypeAndMembers (firstMemberRange: range option) ctx = match firstMemberRange with - | Some _range when (ctx.Config.NewlineBetweenTypeDefinitionAndMembers) -> - sepNln ctx + | Some range when (ctx.Config.NewlineBetweenTypeDefinitionAndMembers) -> + sepNlnConsideringTriviaContentBeforeForMainNode SynMemberDefn_Member range ctx | _ -> ctx @@ -983,16 +1024,16 @@ let internal sepNlnWhenWriteBeforeNewlineNotEmpty fallback (ctx:Context) = else fallback ctx -let internal autoNlnConsideringTriviaIfExpressionExceedsPageWidth expr (key: Choice) range (ctx: Context) = +let internal autoNlnConsideringTriviaIfExpressionExceedsPageWidth sepNlnConsideringTriviaContentBefore expr (ctx: Context) = expressionExceedsPageWidth sepNone sepNone // before and after for short expressions - (sepNlnConsideringTriviaContentBefore key range) sepNone // before and after for long expressions + sepNlnConsideringTriviaContentBefore sepNone // before and after for long expressions expr ctx -let internal addExtraNewlineIfLeadingWasMultiline leading continuation (key: Choice) continuationRange = +let internal addExtraNewlineIfLeadingWasMultiline leading sepNlnConsideringTriviaContentBefore continuation = leadingExpressionIsMultiline leading - (fun ml -> sepNln +> onlyIf ml (sepNlnConsideringTriviaContentBefore key continuationRange) +> continuation) + (fun ml -> sepNln +> onlyIf ml sepNlnConsideringTriviaContentBefore +> continuation) /// This helper function takes a list of expressions and ranges. /// If the expression is multiline it will add a newline before and after the expression. @@ -1015,11 +1056,13 @@ let internal addExtraNewlineIfLeadingWasMultiline leading continuation (key: Cho /// /// The range in the tuple is the range of expression -let internal colWithNlnWhenItemIsMultiline items = +type internal ColMultilineItem = (Context -> Context) * (Context -> Context) * range + +let internal colWithNlnWhenItemIsMultiline (items: ColMultilineItem list) = let firstItemRange = List.tryHead items |> Option.map (fun (_,_,r) -> r) let rec impl items = match items with - | (f1, k1, r1)::(_, k2, r2)::_ -> + | (f1, sepNln1, r1)::(_, sepNln2, _)::_ -> let f1Expr = match firstItemRange with | Some (fr1) when (fr1 = r1) -> @@ -1031,10 +1074,10 @@ let internal colWithNlnWhenItemIsMultiline items = ifElseCtx newlineBetweenLastWriteEvent f1 - (autoNlnConsideringTriviaIfExpressionExceedsPageWidth f1 k1 r1) + (autoNlnConsideringTriviaIfExpressionExceedsPageWidth sepNln1 f1) - addExtraNewlineIfLeadingWasMultiline f1Expr (impl (List.skip 1 items)) k2 r2 - | [(f,k,r)] -> + addExtraNewlineIfLeadingWasMultiline f1Expr sepNln2 (impl (List.skip 1 items)) + | [(f, sepNln,r)] -> match firstItemRange with | Some (fr1) when (fr1 = r) -> // this can only happen when there is only one item in items @@ -1043,7 +1086,7 @@ let internal colWithNlnWhenItemIsMultiline items = ifElseCtx newlineBetweenLastWriteEvent f - (autoNlnConsideringTriviaIfExpressionExceedsPageWidth f k r) + (autoNlnConsideringTriviaIfExpressionExceedsPageWidth sepNln f) | [] -> sepNone impl items @@ -1087,20 +1130,6 @@ let internal lastLineOnlyContains characters (ctx: Context) = let length = String.length lastLine length = 0 || length < ctx.Config.IndentSize -let private (|CommentOrDefineEvent|_|) we = - match we with - | Write (w) when (String.startsWithOrdinal "//" w) -> - Some we - | Write (w) when (String.startsWithOrdinal "#if" w) -> - Some we - | Write (w) when (String.startsWithOrdinal "#else" w) -> - Some we - | Write (w) when (String.startsWithOrdinal "#endif" w) -> - Some we - | Write (w) when (String.startsWithOrdinal "(*" w) -> - Some we - | _ -> None - // Add a newline when the previous code is only one line above the current location // For example // let a = meh diff --git a/src/Fantomas/Queue.fs b/src/Fantomas/Queue.fs index 9b3b9feed3..8391665891 100644 --- a/src/Fantomas/Queue.fs +++ b/src/Fantomas/Queue.fs @@ -44,10 +44,10 @@ type Queue<'T> (data : list<'T[]>, length : int) = member this.Append xs = Queue(Array.ofList xs :: data, length + List.length xs) - /// Equivalent of q |> Queue.toSeq |> Seq.skip n |> Seq.exists f, optimized for speed - member this.SkipExists n f = + /// Equivalent of q |> Queue.toSeq |> Seq.skip n |> Seq.skipWhile p |> Seq.exists f, optimized for speed + member this.SkipExists n f p = if n >= length then false else - let mutable i = length - n // how nany items at end + let mutable i = length - n // how many items at end let mutable r = false let rec dataToEnd acc = function | (hd: _[]) :: tl -> @@ -68,7 +68,7 @@ type Queue<'T> (data : list<'T[]>, length : int) = if r then true else exists tl | [] -> r let d = dataToEnd [] data - d |> exists + d |> List.skipWhile p |> exists interface System.Collections.Generic.IReadOnlyCollection<'T> with member this.Count = this.Length @@ -101,4 +101,4 @@ module Queue = let inline append (q : Queue<'T>) xs = q.Append xs /// Equivalent of q |> Queue.toSeq |> Seq.skip n |> Seq.exists f - let inline skipExists n f (q : Queue<'T>) = q.SkipExists n f + let inline skipExists n f p (q : Queue<'T>) = q.SkipExists n f p diff --git a/src/Fantomas/SourceParser.fs b/src/Fantomas/SourceParser.fs index c4925ed88c..c694232730 100644 --- a/src/Fantomas/SourceParser.fs +++ b/src/Fantomas/SourceParser.fs @@ -820,12 +820,14 @@ let rec collectComputationExpressionStatements e : ComputationExpressionStatemen | expr -> [ OtherStatement expr ] /// Matches if the SynExpr has some or of computation expression member call inside. -let (|CompExprBody|_|) expr = +let rec (|CompExprBody|_|) expr = match expr with - | SynExpr.LetOrUse(_,_,_, SynExpr.LetOrUseBang(_), _) -> + | SynExpr.LetOrUse(_,_,_, CompExprBody(_), _) -> Some expr | SynExpr.LetOrUseBang _ -> Some expr | SynExpr.Sequential(_,_, _, SynExpr.YieldOrReturn(_), _) -> Some expr + | SynExpr.Sequential(_,_, _, SynExpr.LetOrUse(_), _) -> Some expr + | SynExpr.Sequential(_,_, SynExpr.DoBang _, SynExpr.LetOrUseBang _, _) -> Some expr | _ -> None let (|ForEach|_|) = function diff --git a/src/Fantomas/SourceTransformer.fs b/src/Fantomas/SourceTransformer.fs index 497251d696..5712d9d60f 100644 --- a/src/Fantomas/SourceTransformer.fs +++ b/src/Fantomas/SourceTransformer.fs @@ -3,6 +3,7 @@ open FSharp.Compiler.SyntaxTree open Fantomas.Context open Fantomas.SourceParser +open Fantomas.TriviaTypes [] module List = @@ -67,6 +68,11 @@ let rec (|OpenL|_|) = function | Open _ as x::ys -> Some([x], ys) | _ -> None +let rec (|AttributesL|_|) = function + | Attributes _ as x::AttributesL(xs, ys) -> Some(x::xs, ys) + | Attributes _ as x::ys -> Some([x], ys) + | _ -> None + let rec (|SigOpenL|_|) = function | SigOpen _ as x::SigOpenL(xs, ys) -> Some(x::xs, ys) | SigOpen _ as x::ys -> Some([x], ys) @@ -172,4 +178,94 @@ let rec getSynPatLength (synPat: SynPat) = | PatTyped (p,t) -> getSynPatLength p + getSynTypeLength t - | _ -> 0 \ No newline at end of file + | _ -> 0 + +let synModuleDeclToFsAstType = function + | SynModuleDecl.DoExpr _ -> SynModuleDecl_DoExpr + | SynModuleDecl.Types _ -> SynModuleDecl_Types + | SynModuleDecl.NestedModule _ -> SynModuleDecl_NestedModule + | SynModuleDecl.Let _ -> SynModuleDecl_Let + | SynModuleDecl.Open _ -> SynModuleDecl_Open + | SynModuleDecl.ModuleAbbrev _ -> SynModuleDecl_ModuleAbbrev + | SynModuleDecl.Exception _ -> SynModuleDecl_Exception + | SynModuleDecl.Attributes _ -> SynModuleDecl_Attributes + | SynModuleDecl.HashDirective _ -> SynModuleDecl_HashDirective + | SynModuleDecl.NamespaceFragment _ -> SynModuleDecl_NamespaceFragment + +let synMemberDefnToFsAstType = function + | SynMemberDefn.Member _ -> SynMemberDefn_Member + | SynMemberDefn.Open _ -> SynMemberDefn_Open + | SynMemberDefn.ImplicitCtor _ -> SynMemberDefn_ImplicitCtor + | SynMemberDefn.ImplicitInherit _ -> SynMemberDefn_ImplicitInherit + | SynMemberDefn.LetBindings _ -> SynMemberDefn_LetBindings + | SynMemberDefn.AbstractSlot _ -> SynMemberDefn_AbstractSlot + | SynMemberDefn.Interface _ -> SynMemberDefn_Interface + | SynMemberDefn.Inherit _ -> SynMemberDefn_Inherit + | SynMemberDefn.ValField _ -> SynMemberDefn_ValField + | SynMemberDefn.NestedType _ -> SynMemberDefn_NestedType + | SynMemberDefn.AutoProperty _ -> SynMemberDefn_AutoProperty + +let synExprToFsAstType = function + | SynExpr.YieldOrReturn _ -> SynExpr_YieldOrReturn + | SynExpr.IfThenElse _ -> SynExpr_IfThenElse + | SynExpr.LetOrUseBang _ -> SynExpr_LetOrUseBang + | SynExpr.Const _ -> SynExpr_Const + | SynExpr.Lambda _ -> SynExpr_Lambda + | SynExpr.Ident _ -> SynExpr_Ident + | SynExpr.App _ -> SynExpr_App + | SynExpr.Match _ -> SynExpr_Match + | SynExpr.Record _ -> SynExpr_Record + | SynExpr.Tuple _ -> SynExpr_Tuple + | SynExpr.DoBang _ -> SynExpr_DoBang + | SynExpr.Paren _ -> SynExpr_Paren + | SynExpr.AnonRecd _ -> SynExpr_AnonRecd + | SynExpr.ArrayOrListOfSeqExpr _ -> SynExpr_ArrayOrListOfSeqExpr + | SynExpr.LongIdentSet _ -> SynExpr_LongIdentSet + | SynExpr.New _ -> SynExpr_New + | SynExpr.Quote _ -> SynExpr_Quote + | SynExpr.DotIndexedSet _ -> SynExpr_DotIndexedSet + | SynExpr.LetOrUse _ -> SynExpr_LetOrUse + | SynExpr.TryWith _ -> SynExpr_TryWith + | SynExpr.YieldOrReturnFrom _ -> SynExpr_YieldOrReturnFrom + | SynExpr.While _ -> SynExpr_While + | SynExpr.TryFinally _ -> SynExpr_TryFinally + | SynExpr.Do _ -> SynExpr_Do + | SynExpr.AddressOf _ -> SynExpr_AddressOf + | SynExpr.Typed _ -> SynExpr_Typed + | SynExpr.ArrayOrList _ -> SynExpr_ArrayOrList + | SynExpr.ObjExpr _ -> SynExpr_ObjExpr + | SynExpr.For _ -> SynExpr_For + | SynExpr.ForEach _ -> SynExpr_ForEach + | SynExpr.CompExpr _ -> SynExpr_CompExpr + | SynExpr.MatchLambda _ -> SynExpr_MatchLambda + | SynExpr.Assert _ -> SynExpr_Assert + | SynExpr.TypeApp _ -> SynExpr_TypeApp + | SynExpr.Lazy _ -> SynExpr_Lazy + | SynExpr.LongIdent _ -> SynExpr_LongIdent + | SynExpr.DotGet _ -> SynExpr_DotGet + | SynExpr.DotSet _ -> SynExpr_DotSet + | SynExpr.Set _ -> SynExpr_Set + | SynExpr.DotIndexedGet _ -> SynExpr_DotIndexedGet + | SynExpr.NamedIndexedPropertySet _ -> SynExpr_NamedIndexedPropertySet + | SynExpr.DotNamedIndexedPropertySet _ -> SynExpr_DotNamedIndexedPropertySet + | SynExpr.TypeTest _ -> SynExpr_TypeTest + | SynExpr.Upcast _ -> SynExpr_Upcast + | SynExpr.Downcast _ -> SynExpr_Downcast + | SynExpr.InferredUpcast _ -> SynExpr_InferredUpcast + | SynExpr.InferredDowncast _ -> SynExpr_InferredDowncast + | SynExpr.Null _ -> SynExpr_Null + | SynExpr.TraitCall _ -> SynExpr_TraitCall + | SynExpr.JoinIn _ -> SynExpr_JoinIn + | SynExpr.ImplicitZero _ -> SynExpr_ImplicitZero + | SynExpr.SequentialOrImplicitYield _ -> SynExpr_SequentialOrImplicitYield + | SynExpr.MatchBang _ -> SynExpr_MatchBang + | SynExpr.LibraryOnlyILAssembly _ -> SynExpr_LibraryOnlyILAssembly + | SynExpr.LibraryOnlyStaticOptimization _ -> SynExpr_LibraryOnlyStaticOptimization + | SynExpr.LibraryOnlyUnionCaseFieldGet _ -> SynExpr_LibraryOnlyUnionCaseFieldGet + | SynExpr.LibraryOnlyUnionCaseFieldSet _ -> SynExpr_LibraryOnlyUnionCaseFieldSet + | SynExpr.ArbitraryAfterError _ -> SynExpr_ArbitraryAfterError + | SynExpr.FromParseError _ -> SynExpr_FromParseError + | SynExpr.DiscardAfterMissingQualificationAfterDot _ -> SynExpr_DiscardAfterMissingQualificationAfterDot + | SynExpr.Fixed _ -> SynExpr_Fixed + | SynExpr.InterpolatedString _ -> SynExpr_InterpolatedString + | SynExpr.Sequential _ -> SynExpr_Sequential \ No newline at end of file diff --git a/src/Fantomas/Trivia.fs b/src/Fantomas/Trivia.fs index 48e1a75350..8924791ec9 100644 --- a/src/Fantomas/Trivia.fs +++ b/src/Fantomas/Trivia.fs @@ -185,8 +185,8 @@ let private mapNodeToTriviaNode (node: Node) = let private commentIsAfterLastTriviaNode (triviaNodes: TriviaNodeAssigner list) (range: range) = let hasNoNodesAfterRange = triviaNodes - |> Seq.filter (fun tn -> tn.Range.EndLine > range.StartLine && isMainNodeButNotAnonModule tn) - |> Seq.isEmpty + |> Seq.exists (fun tn -> tn.Range.EndLine > range.StartLine && isMainNodeButNotAnonModule tn) + |> not let hasOnlyOneNamedModule = triviaNodes diff --git a/src/Fantomas/TriviaContext.fs b/src/Fantomas/TriviaContext.fs index cedd461e51..4cea513d6b 100644 --- a/src/Fantomas/TriviaContext.fs +++ b/src/Fantomas/TriviaContext.fs @@ -4,37 +4,10 @@ open Fantomas open Fantomas.Context open Fantomas.TriviaTypes open FSharp.Compiler.Range -open FSharp.Compiler.SyntaxTree let tokN (range: range) (tokenName: FsTokenType) f = enterNodeTokenByName range tokenName +> f +> leaveNodeTokenByName range tokenName -let firstNewlineOrComment (es: SynExpr list) (ctx: Context) = - let triviaNodes = - [ yield! (Map.tryFindOrEmptyList SynExpr_App ctx.TriviaMainNodes) - yield! (Map.tryFindOrEmptyList SynExpr_DoBang ctx.TriviaMainNodes) - yield! (Map.tryFindOrEmptyList SynExpr_YieldOrReturnFrom ctx.TriviaMainNodes) ] - - es - |> List.tryHead - |> Option.bind (fun e -> TriviaHelpers.findByRange triviaNodes e.Range) - |> fun cb -> - match cb with - | Some ({ ContentBefore = (TriviaContent.Newline|TriviaContent.Comment _) as head ::rest } as tn) -> - let mapNode t = if t = tn then { tn with ContentBefore = rest } else t - - let updatedTriviaMainNodes = - match tn.Type with - | MainNode(mn) -> - let nodes = Map.find mn ctx.TriviaMainNodes - List.map mapNode nodes - |> fun newNodes -> Map.add mn newNodes ctx.TriviaMainNodes - | _ -> ctx.TriviaMainNodes - - let ctx' = { ctx with TriviaMainNodes = updatedTriviaMainNodes } - printTriviaContent head ctx' - | _ -> sepNone ctx - let triviaAfterArrow (range: range) (ctx: Context) = let hasCommentAfterArrow = findTriviaTokenFromName RARROW range ctx diff --git a/src/Fantomas/TriviaTypes.fs b/src/Fantomas/TriviaTypes.fs index 6e86135b24..4c627cab23 100644 --- a/src/Fantomas/TriviaTypes.fs +++ b/src/Fantomas/TriviaTypes.fs @@ -124,6 +124,7 @@ type FsAstType = | SynExpr_For | SynExpr_ForEach | SynExpr_ArrayOrListOfSeqExpr + | SynExpr_ArrayOrList | SynExpr_CompExpr | SynExpr_Lambda | SynExpr_MatchLambda