From 9638607b95f29e3e5de4a724c3520a6d75b2fcee Mon Sep 17 00:00:00 2001 From: "sergey.t" Date: Thu, 31 May 2018 12:03:38 +0200 Subject: [PATCH 1/3] fix-143 --- src/Fantomas.Cmd/Program.fs | 4 + src/Fantomas.Tests/Fantomas.Tests.fsproj | 1 + src/Fantomas.Tests/PreserveEOLTests.fs | 430 +++++++++++++++++++++++ src/Fantomas/CodeFormatterImpl.fs | 2 +- src/Fantomas/CodePrinter.fs | 2 +- src/Fantomas/FormatConfig.fs | 4 +- src/Fantomas/TokenMatcher.fs | 109 +++++- 7 files changed, 531 insertions(+), 21 deletions(-) create mode 100644 src/Fantomas.Tests/PreserveEOLTests.fs diff --git a/src/Fantomas.Cmd/Program.fs b/src/Fantomas.Cmd/Program.fs index 3ac4b3b526..56e81d8ff6 100644 --- a/src/Fantomas.Cmd/Program.fs +++ b/src/Fantomas.Cmd/Program.fs @@ -39,6 +39,7 @@ let [] stdOutText = " Write the formatted source code to standard outpu let [] indentText = "Set number of spaces for indentation (default = 4). The value should be between 1 and 10." let [] widthText = "Set the column where we break to new lines (default = 80). The value should be at least 60." +let [] preserveEOLText = "Preserve original end of lines, disables auto insert/remove of blank lines (default = false)" let [] semicolonEOLText = "Enable semicolons at the end of line (default = false)." let [] argumentText = "Disable spaces before the first argument of functions when there are parenthesis (default = true). For methods and constructors, there are never spaces regardless of this option." let [] colonText = "Disable spaces before colons (default = true)." @@ -108,6 +109,7 @@ let main _args = let indent = ref 4 let pageWidth = ref 80 + let preserveEOL = ref false let semicolonEOL = ref false let spaceBeforeArgument = ref true let spaceBeforeColon = ref true @@ -225,6 +227,7 @@ let main _args = ArgInfo("--indent", ArgType.Int handleIndent, indentText); ArgInfo("--pageWidth", ArgType.Int handlePageWidth, widthText); + ArgInfo("--preserveEOL", ArgType.Set preserveEOL, preserveEOLText) ArgInfo("--semicolonEOL", ArgType.Set semicolonEOL, semicolonEOLText); ArgInfo("--noSpaceBeforeArgument", ArgType.Clear spaceBeforeArgument, argumentText); ArgInfo("--noSpaceBeforeColon", ArgType.Clear spaceBeforeColon, colonText); @@ -242,6 +245,7 @@ let main _args = { FormatConfig.Default with IndentSpaceNum = !indent; PageWidth = !pageWidth; + PreserveEndOfLine = !preserveEOL; SemicolonAtEndOfLine = !semicolonEOL; SpaceBeforeArgument = !spaceBeforeArgument; SpaceBeforeColon = !spaceBeforeColon; diff --git a/src/Fantomas.Tests/Fantomas.Tests.fsproj b/src/Fantomas.Tests/Fantomas.Tests.fsproj index 2a07f9b736..fca67a07a0 100644 --- a/src/Fantomas.Tests/Fantomas.Tests.fsproj +++ b/src/Fantomas.Tests/Fantomas.Tests.fsproj @@ -35,6 +35,7 @@ + diff --git a/src/Fantomas.Tests/PreserveEOLTests.fs b/src/Fantomas.Tests/PreserveEOLTests.fs new file mode 100644 index 0000000000..2006c8f180 --- /dev/null +++ b/src/Fantomas.Tests/PreserveEOLTests.fs @@ -0,0 +1,430 @@ +module Fantomas.Tests.PreserveBlankLinesTests + +open NUnit.Framework +open FsUnit + +open Fantomas.CodeFormatter +open Fantomas.Tests.TestHelper + +let config = { config with PreserveEndOfLine = true; PageWidth = 20 } + +[] +let ``preserve blank lines`` () = + formatSourceString false """ +let x= 1 + +let y=2""" config + |> should equal """ +let x = 1 + +let y = 2 +""" + +[] +let ``preserve multiple blank lines`` () = + formatSourceString false """ +type Num=int + + + +printfn 0""" config + |> should equal """ +type Num = int + + + +printfn 0 +""" + +[] +let ``disable auto blank line if preserve enabled`` () = + formatSourceString false """ +let x= + let z =1 + z +let y =2""" config + |> should equal """ +let x = + let z = 1 + z +let y = 2 +""" + +[] +let ``preserve blank lines in nested case`` () = + formatSourceString false """ +let x= + let z =1 + + z + +let y =2""" config + |> should equal """ +let x = + let z = 1 + + z + +let y = 2 +""" + +[] +let ``disabled removing of line breaks`` () = + formatSourceString false """ +let x= + 1 + +let y=2""" config + |> should equal """ +let x = + 1 + +let y = 2 +""" + +[] +let ``comments on local let bindings``() = + formatSourceString false """ +let f() = + + /// c1 + /// c2 + let x = "/// c3 " + 1""" config + |> should equal """ +let f() = + + /// c1 + /// c2 + let x = "/// c3 " + 1 +""" + + +[] +let ``should keep comments before attributes``() = + formatSourceString false """ +[] +type IlxGenOptions = + { fragName: string + generateFilterBlocks: bool + workAroundReflectionEmitBugs: bool + emitConstantArraysUsingStaticDataBlobs: bool + // If this is set, then the last module becomes the "main" module and its toplevel bindings are executed at startup + mainMethodInfo: Tast.Attribs option + localOptimizationsAreOn: bool + generateDebugSymbols: bool + testFlagEmitFeeFeeAs100001: bool + ilxBackend: IlxGenBackend + /// Indicates the code is being generated in FSI.EXE and is executed immediately after code generation + /// This includes all interactively compiled code, including #load, definitions, and expressions + isInteractive: bool + // Indicates the code generated is an interactive 'it' expression. We generate a setter to allow clearing of the underlying + // storage, even though 'it' is not logically mutable + isInteractiveItExpr: bool + // Indicates System.SerializableAttribute is available in the target framework + netFxHasSerializableAttribute : bool + /// Whenever possible, use callvirt instead of call + alwaysCallVirt: bool} +""" { config with SemicolonAtEndOfLine = true } + |> should equal """ +[] +type IlxGenOptions = + { fragName : string; + generateFilterBlocks : bool; + workAroundReflectionEmitBugs : bool; + emitConstantArraysUsingStaticDataBlobs : bool; + // If this is set, then the last module becomes the "main" module and its toplevel bindings are executed at startup + mainMethodInfo : Tast.Attribs option; + localOptimizationsAreOn : bool; + generateDebugSymbols : bool; + testFlagEmitFeeFeeAs100001 : bool; + ilxBackend : IlxGenBackend; + /// Indicates the code is being generated in FSI.EXE and is executed immediately after code generation + /// This includes all interactively compiled code, including #load, definitions, and expressions + isInteractive : bool; + // Indicates the code generated is an interactive 'it' expression. We generate a setter to allow clearing of the underlying + // storage, even though 'it' is not logically mutable + isInteractiveItExpr : bool; + // Indicates System.SerializableAttribute is available in the target framework + netFxHasSerializableAttribute : bool; + /// Whenever possible, use callvirt instead of call + alwaysCallVirt : bool } +""" + +[] +let ``should keep well-aligned comments``() = + formatSourceString false """ +/// XML COMMENT +// Other comment +let f() = + // COMMENT A + let y = 1 + (* COMMENT B *) + (* COMMENT C *) + x + x + x +""" config + |> should equal """ +/// XML COMMENT +// Other comment +let f() = + // COMMENT A + let y = 1 + (* COMMENT B *) + (* COMMENT C *) + x + x + x +""" + +[] +let ``active patterns``() = + formatSourceString false """ +let (|Even|Odd| ) input = if input % 2=0 then Even else Odd + +let (|Integer|_|) (str:string) = + let mutable intvalue = 0 + if System.Int32.TryParse(str, &intvalue) then Some(intvalue) + else None + +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""" config + |> should equal """ +let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd + +let (|Integer|_|) (str : string) = + let mutable intvalue = 0 + if System.Int32.TryParse (str, &intvalue) then Some (intvalue) + else None + +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 +""" + +[] +let ``should keep the attribute on top of the function``() = + formatSourceString false """[] +type Funcs = + [] + static member ToFunc (f: Action<_,_,_>) = + Func<_,_,_,_>(fun a b c -> f.Invoke(a,b,c)) + """ config + |> should equal """[] +type Funcs = + [] + static member ToFunc(f : Action<_, _, _>) = + Func<_, _, _, _> (fun a b c -> f.Invoke (a, b, c)) +""" + +[] +let ``attributes on expressions``() = + formatSourceString false """ + [] + let x="1" + let []y="2" + [] + do ()""" config + |> should equal """ + [] + let x = "1" + let [] y = "2" + [] + do () +""" + +[] +let ``array values``() = + formatSourceString false """ +let x = [ 1 + 2] +let y = [| 3;4 + 5;6|] +let z = [7; 8] + """ { config with SemicolonAtEndOfLine = false } + |> should equal """ +let x = [ 1; + 2 ] +let y = [| 3; 4; + 5; 6 |] +let z = [ 7; 8 ] +""" + +[] +let ``array values auto break``() = + formatSourceString false """ +let arr = [|(1, 1,1); (1,2, 2); (1, 3, 3); (2, 1, 2); (2,2,4); (2, 3, 6); (3, 1, 3); + (3, 2, 6); (3, 3, 9)|] + """ { config with SemicolonAtEndOfLine = false } + |> should equal """ +let arr = [| (1, 1, 1); (1, 2, 2); (1, 3, 3); (2, 1, 2); (2, 2, 4); (2, 3, 6); (3, 1, 3); + (3, 2, 6); (3, 3, 9) |] +""" +[] +let ``handle # identifier``() = + formatSourceString false """namespace global + +#if DEBUG +#endif""" config + |> should equal """namespace global + +#if DEBUG +#endif +""" + +[] +let ``don't remove semicolon in list``() = + formatSourceString false """ +let x = [yield! es; yield (s, e')] + """ { config with SemicolonAtEndOfLine = false } + |> should equal """ +let x = [ yield! es; yield (s, e') ] +""" + +[] +let ``don't remove semicolon in list with auto semicolon ON``() = + formatSourceString false """ +let x = [yield! es; yield (s, e')] + """ { config with SemicolonAtEndOfLine = true } + |> should equal """ +let x = [ yield! es; yield (s, e') ] +""" + +[] +let ``indentation issue 1``() = + formatSourceString false """ +let f1 = + let f2 = + if pred then 1 + else 2 + + let f3 = + 3 + 4 + """ config + |> should equal """ +let f1 = + let f2 = + if pred then 1 + else 2 + + let f3 = + 3 + 4 +""" + +[] +let ``indentation issue 2 (core bug)``() = + formatSourceString false """ + f1 p1 " +" config + """ config + |> should equal """ + f1 p1 " +" config +""" + +[] +let ``indentation issue 3 (core bug)``() = + formatSourceString false """ +[] +let ``^a 1``() =() +[] +let ``b 2`` ()= () + """ config + |> should equal """ +[] +let ``^a 1``() = () +[] +let ``b 2``() = () +""" + +[] +let ``indentation issue 4``() = + formatSourceString false """ +type T = + static member f(p1, + p2) = + 1 + """ config + |> should equal """ +type T = + static member f (p1, + p2) = + 1 +""" + +[] +let ``semicolon issue 1``() = + formatSourceString false """ +seq {yield! 1;yield! 2} + """ config + |> should equal """ +seq { yield! 1; yield! 2} +""" + +[] +let ``indentation issue 5 (# handling)``() = + formatSourceString false """ + #if F + #else + let f1 = + let x = ref 0 + 0 + #endif + """ config + |> should equal """ + #if F + #else + let f1 = + let x = ref 0 + 0 + #endif +""" + +[] +let ``indentation issue 6``() = + formatSourceString false """ +(* + +A + + C + +D +*) + +let x=1""" config + |> should equal """ +(* + +A + + C + +D +*) + +let x = 1 +""" + +[] +let ``core issue 1``() = + formatSourceString false """ +try + x := 1 +with +| :? E -> + y := f.ReadToEnd() +""" { config with PreserveEndOfLine = false } + |> prepend newline + |> should equal """ +try + x := 1 +with +| :? E -> + y := f.ReadToEnd() +""" diff --git a/src/Fantomas/CodeFormatterImpl.fs b/src/Fantomas/CodeFormatterImpl.fs index 0eb90a8018..11aa105d5c 100644 --- a/src/Fantomas/CodeFormatterImpl.fs +++ b/src/Fantomas/CodeFormatterImpl.fs @@ -357,7 +357,7 @@ let formatWith ast moduleName input config = Context.create config normalizedSourceCode |> genParsedInput { ASTContext.Default with TopLevelModuleName = moduleName } ast |> dump - |> if config.StrictMode then id else integrateComments normalizedSourceCode + |> if config.StrictMode then id else integrateComments config.PreserveEndOfLine normalizedSourceCode // Sometimes F# parser gives a partial AST for incorrect input if input.IsSome && String.IsNullOrWhiteSpace normalizedSourceCode <> String.IsNullOrWhiteSpace formattedSourceCode then diff --git a/src/Fantomas/CodePrinter.fs b/src/Fantomas/CodePrinter.fs index 2dbbe51ca7..fa76a1e807 100644 --- a/src/Fantomas/CodePrinter.fs +++ b/src/Fantomas/CodePrinter.fs @@ -597,7 +597,7 @@ and genExpr astContext = function | SequentialSimple es -> atCurrentColumn (colAutoNlnSkip0 sepSemi es (genExpr astContext)) // It seems too annoying to use sepSemiNln - | Sequentials es -> atCurrentColumn (col sepNln es (genExpr astContext)) + | Sequentials es -> atCurrentColumn (col sepSemiNln es (genExpr astContext)) // A generalization of IfThenElse | ElIf((e1,e2, _, _)::es, enOpt) -> atCurrentColumn (!- "if " +> ifElse (checkBreakForExpr e1) (genExpr astContext e1 ++ "then") (genExpr astContext e1 +- "then") -- " " diff --git a/src/Fantomas/FormatConfig.fs b/src/Fantomas/FormatConfig.fs index f7ff8b6d0c..bbb0fc07aa 100644 --- a/src/Fantomas/FormatConfig.fs +++ b/src/Fantomas/FormatConfig.fs @@ -20,6 +20,7 @@ type FormatConfig = IndentSpaceNum : Num; /// The column where we break to new lines PageWidth : Num; + PreserveEndOfLine : bool; SemicolonAtEndOfLine : bool; SpaceBeforeArgument : bool; SpaceBeforeColon : bool; @@ -34,6 +35,7 @@ type FormatConfig = static member Default = { IndentSpaceNum = 4; PageWidth = 80; + PreserveEndOfLine = false; SemicolonAtEndOfLine = false; SpaceBeforeArgument = true; SpaceBeforeColon = true; SpaceAfterComma = true; SpaceAfterSemicolon = true; IndentOnTryWith = false; ReorderOpenDeclaration = false; @@ -391,7 +393,7 @@ let internal sepSemi (ctx : Context) = let internal sepSemiNln (ctx : Context) = // sepNln part is essential to indentation - if ctx.Config.SemicolonAtEndOfLine then (!- ";" +> sepNln) ctx else sepNln ctx + if ctx.Config.SemicolonAtEndOfLine || ctx.Config.PreserveEndOfLine then (!- ";" +> sepNln) ctx else sepNln ctx let internal sepBeforeArg (ctx : Context) = if ctx.Config.SpaceBeforeArgument then str " " ctx else str "" ctx diff --git a/src/Fantomas/TokenMatcher.fs b/src/Fantomas/TokenMatcher.fs index 9124190be5..a0332f6dca 100644 --- a/src/Fantomas/TokenMatcher.fs +++ b/src/Fantomas/TokenMatcher.fs @@ -7,6 +7,7 @@ open Fantomas open Microsoft.FSharp.Compiler.Range open Microsoft.FSharp.Compiler.PrettyNaming open Microsoft.FSharp.Compiler.SourceCodeServices +open System.Text.RegularExpressions #if INTERACTIVE type Debug = Console @@ -424,10 +425,17 @@ let (|OpenChunk|_|) = function /// Assume that originalText and newText are derived from the same AST. /// Pick all comments and directives from originalText to insert into newText -let integrateComments (originalText : string) (newText : string) = - let origTokens = tokenize (filterConstants originalText) originalText |> markStickiness |> Seq.toList +let integrateComments isPreserveEOL (originalText : string) (newText : string) = + let trim (txt : string) = + if not isPreserveEOL then txt + else Regex.Replace(String.normalizeNewLine txt, @"[ \t]+$", "", RegexOptions.Multiline) + + let trimOrig = trim originalText + let trimNew = trim newText + + let origTokens = tokenize (filterConstants trimOrig) trimOrig |> markStickiness |> Seq.toList //Seq.iter (fun (Marked(_, s, t)) -> Console.WriteLine("sticky information: {0} -- {1}", s, t)) origTokens - let newTokens = tokenize [] newText |> Seq.toList + let newTokens = tokenize [] trimNew |> Seq.toList let buffer = System.Text.StringBuilder() let column = ref 0 @@ -442,9 +450,10 @@ let integrateComments (originalText : string) (newText : string) = let maintainIndent f = let c = !column f() - Debug.WriteLine("maintain indent at {0}", c) - addText Environment.NewLine - addText (String.replicate c " ") + if not isPreserveEOL then + Debug.WriteLine("maintain indent at {0}", c) + addText Environment.NewLine + addText (String.replicate c " ") let saveIndent c = indent := c @@ -456,6 +465,27 @@ let integrateComments (originalText : string) (newText : string) = addText (String.replicate c " ") f() + let preserveLineBreaks ots (nts:(Token * string) list) = + let rec newSpacingLength = + match nts with + | (EOL, _)::(Space t)::(Tok(_, _), "[<")::_ -> 2 + | (EOL, _)::(EOL, _)::(Tok(_, _), "[<")::_-> 0 + | (EOL, _)::_ -> 1 + | (Tok(_, _), ";")::_ -> + addText ";" + 1 + | (Space t)::_ -> String.length t + | _ -> 0 + + buffer.Append Environment.NewLine |> ignore + + match ots with + | SpaceToken t::_ -> + let nsLen = newSpacingLength + let oi = if nsLen <= String.length t then t.Substring(nsLen) else t + addText oi + | _ -> () + // Assume that starting whitespaces after EOL give indentation of a chunk let rec getIndent = function | (Token _, _) :: moreNewTokens -> getIndent moreNewTokens @@ -490,8 +520,17 @@ let integrateComments (originalText : string) (newText : string) = | (NewLineToken _ :: moreOrigTokens), _ -> Debug.WriteLine "dropping newline from orig tokens" - loop moreOrigTokens newTokens - + let nextNewTokens = + if isPreserveEOL then + preserveLineBreaks moreOrigTokens newTokens + match newTokens with + | (Tok(_, _), ";")::rs -> rs + | _ -> newTokens + else + newTokens + + loop moreOrigTokens nextNewTokens + // Not a comment, drop the original token text until something matches | (Delimiter tokText :: moreOrigTokens), _ when tokText = ";" || tokText = ";;" -> Debug.WriteLine("dropping '{0}' from original text", box tokText) @@ -503,7 +542,8 @@ let integrateComments (originalText : string) (newText : string) = | (PreprocessorDirectiveChunk (tokensText, moreOrigTokens)), newTokens -> let text = String.concat "" tokensText Debug.WriteLine("injecting preprocessor directive '{0}'", box text) - addText Environment.NewLine + if not isPreserveEOL then + addText Environment.NewLine for x in tokensText do addText x let moreNewTokens = if String.startsWithOrdinal "#endif" text then @@ -538,15 +578,27 @@ let integrateComments (originalText : string) (newText : string) = // What is current indentation of this chunk let numSpaces = countStartingSpaces lines Debug.WriteLine("the number of starting spaces is {0}", numSpaces) - // Write the chunk in the same indentation with #if branch - for line in lines do + Seq.iteri (fun i (line : string) -> if String.startsWithOrdinal "#" line.[numSpaces..] then // Naive recognition of inactive preprocessors addText Environment.NewLine addText line.[numSpaces..] + else if isPreserveEOL && i = 0 then + addText line + else restoreIndent (fun () -> addText line.[numSpaces..]) + ) lines + + let nextNewTokens = + if isPreserveEOL then + restoreIndent id + + let sc = Seq.length tokensText + let tc = Seq.length newTokens + newTokens |> List.skip (if sc >= tc then tc else sc) else - restoreIndent (fun () -> addText line.[numSpaces..]) - loop moreOrigTokens newTokens + newTokens + + loop moreOrigTokens nextNewTokens | (LineCommentChunk true (commentTokensText, moreOrigTokens)), [] -> Debug.WriteLine("injecting the last stick-to-the-left line comment '{0}'", String.concat "" commentTokensText |> box) @@ -584,8 +636,18 @@ let integrateComments (originalText : string) (newText : string) = // Emit end-of-line from new tokens | _, (NewLine newTokText :: moreNewTokens) -> Debug.WriteLine("emitting newline in new tokens '{0}'", newTokText) - addText newTokText - loop origTokens moreNewTokens + let nextNewTokens = + if not isPreserveEOL then + addText newTokText + moreNewTokens + else + match moreNewTokens with + | Space t::rs -> + addText " " + rs + | _ -> moreNewTokens + + loop origTokens nextNewTokens | _, ((Token newTok, newTokText) :: moreNewTokens) when newTok.CharClass = FSharpTokenCharKind.WhiteSpace && newTok.ColorClass <> FSharpTokenColorKind.InactiveCode -> @@ -640,17 +702,26 @@ let integrateComments (originalText : string) (newText : string) = // Drop the last newline if i = len - 1 && x = Environment.NewLine then () else addText x)) - loop moreOrigTokens newTokens + let nextOrigTokens = + if isPreserveEOL && List.last commentTokensText = Environment.NewLine then + Marked(EOL, Environment.NewLine, LineCommentStickiness.NotApplicable)::moreOrigTokens + else moreOrigTokens + + loop nextOrigTokens newTokens // Consume attributes in the new text | _, RawAttribute(newTokensText, moreNewTokens) -> Debug.WriteLine("no matching of attribute tokens") - for x in newTokensText do addText x + if not isPreserveEOL then + for x in newTokensText do addText x loop origTokens moreNewTokens // Skip attributes in the old text | (Attribute (tokensText, moreOrigTokens)), _ -> Debug.WriteLine("skip matching of attribute tokens '{0}'", box tokensText) + if isPreserveEOL then + for x in tokensText do addText x + addText " " loop moreOrigTokens newTokens // Open declarations may be reordered, so we match them even if two identifiers are different @@ -695,4 +766,6 @@ let integrateComments (originalText : string) (newText : string) = () loop origTokens newTokens - buffer.ToString() + buffer.ToString() + |> trim + |> fun x -> if not isPreserveEOL then x else x.Replace("\n", Environment.NewLine) From 42e78298610e7ac9d93e2a71f26ef2564f428468 Mon Sep 17 00:00:00 2001 From: "sergey.t" Date: Thu, 21 Jun 2018 14:54:51 +0200 Subject: [PATCH 2/3] fix: semicolon handling --- src/Fantomas.Tests/PreserveEOLTests.fs | 32 ++++++++++++++++++++++++++ src/Fantomas/FormatConfig.fs | 2 +- src/Fantomas/TokenMatcher.fs | 4 +++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Fantomas.Tests/PreserveEOLTests.fs b/src/Fantomas.Tests/PreserveEOLTests.fs index 2006c8f180..a786479501 100644 --- a/src/Fantomas.Tests/PreserveEOLTests.fs +++ b/src/Fantomas.Tests/PreserveEOLTests.fs @@ -428,3 +428,35 @@ with | :? E -> y := f.ReadToEnd() """ + +[] +let ``records formatting without pEOL``() = + formatSourceString false """ +let rainbow = + { b1 = "1" + b2 = "2" + b3 = "3" + } + """ { config with PreserveEndOfLine = false } + |> should equal """let rainbow = + { b1 = "1" + b2 = "2" + b3 = "3" } +""" + +[] +let ``records formatting with pEOL``() = + formatSourceString false """ +let rainbow = + { b1 = "1" + b2 = "2" + b3 = "3" + } + """ config + |> should equal """ +let rainbow = + { b1 = "1" + b2 = "2" + b3 = "3" + } +""" diff --git a/src/Fantomas/FormatConfig.fs b/src/Fantomas/FormatConfig.fs index bbb0fc07aa..7b88cf6fb6 100644 --- a/src/Fantomas/FormatConfig.fs +++ b/src/Fantomas/FormatConfig.fs @@ -393,7 +393,7 @@ let internal sepSemi (ctx : Context) = let internal sepSemiNln (ctx : Context) = // sepNln part is essential to indentation - if ctx.Config.SemicolonAtEndOfLine || ctx.Config.PreserveEndOfLine then (!- ";" +> sepNln) ctx else sepNln ctx + if ctx.Config.SemicolonAtEndOfLine then (!- ";" +> sepNln) ctx else sepNln ctx let internal sepBeforeArg (ctx : Context) = if ctx.Config.SpaceBeforeArgument then str " " ctx else str "" ctx diff --git a/src/Fantomas/TokenMatcher.fs b/src/Fantomas/TokenMatcher.fs index a0332f6dca..34368e8e9f 100644 --- a/src/Fantomas/TokenMatcher.fs +++ b/src/Fantomas/TokenMatcher.fs @@ -532,8 +532,10 @@ let integrateComments isPreserveEOL (originalText : string) (newText : string) = loop moreOrigTokens nextNewTokens // Not a comment, drop the original token text until something matches - | (Delimiter tokText :: moreOrigTokens), _ when tokText = ";" || tokText = ";;" -> + | (Delimiter tokText :: moreOrigTokens), (_, nt)::_ when tokText = ";" || tokText = ";;" -> Debug.WriteLine("dropping '{0}' from original text", box tokText) + if isPreserveEOL && nt <> ";" then + addText ";" loop moreOrigTokens newTokens // Inject #if... #else or #endif directive From d4ca3228e3f0b6bd9ffa32750669cf7066d1d41d Mon Sep 17 00:00:00 2001 From: "sergey.t" Date: Fri, 29 Jun 2018 09:31:00 +0200 Subject: [PATCH 3/3] tidied up the test suite --- src/Fantomas.Tests/PreserveEOLTests.fs | 89 +++++--------------------- 1 file changed, 15 insertions(+), 74 deletions(-) diff --git a/src/Fantomas.Tests/PreserveEOLTests.fs b/src/Fantomas.Tests/PreserveEOLTests.fs index a786479501..7f0579b34e 100644 --- a/src/Fantomas.Tests/PreserveEOLTests.fs +++ b/src/Fantomas.Tests/PreserveEOLTests.fs @@ -126,28 +126,28 @@ type IlxGenOptions = netFxHasSerializableAttribute : bool /// Whenever possible, use callvirt instead of call alwaysCallVirt: bool} -""" { config with SemicolonAtEndOfLine = true } +""" config |> should equal """ [] type IlxGenOptions = - { fragName : string; - generateFilterBlocks : bool; - workAroundReflectionEmitBugs : bool; - emitConstantArraysUsingStaticDataBlobs : bool; + { fragName : string + generateFilterBlocks : bool + workAroundReflectionEmitBugs : bool + emitConstantArraysUsingStaticDataBlobs : bool // If this is set, then the last module becomes the "main" module and its toplevel bindings are executed at startup - mainMethodInfo : Tast.Attribs option; - localOptimizationsAreOn : bool; - generateDebugSymbols : bool; - testFlagEmitFeeFeeAs100001 : bool; - ilxBackend : IlxGenBackend; + mainMethodInfo : Tast.Attribs option + localOptimizationsAreOn : bool + generateDebugSymbols : bool + testFlagEmitFeeFeeAs100001 : bool + ilxBackend : IlxGenBackend /// Indicates the code is being generated in FSI.EXE and is executed immediately after code generation /// This includes all interactively compiled code, including #load, definitions, and expressions - isInteractive : bool; + isInteractive : bool // Indicates the code generated is an interactive 'it' expression. We generate a setter to allow clearing of the underlying // storage, even though 'it' is not logically mutable - isInteractiveItExpr : bool; + isInteractiveItExpr : bool // Indicates System.SerializableAttribute is available in the target framework - netFxHasSerializableAttribute : bool; + netFxHasSerializableAttribute : bool /// Whenever possible, use callvirt instead of call alwaysCallVirt : bool } """ @@ -244,7 +244,7 @@ let x = [ 1 let y = [| 3;4 5;6|] let z = [7; 8] - """ { config with SemicolonAtEndOfLine = false } + """ config |> should equal """ let x = [ 1; 2 ] @@ -258,7 +258,7 @@ let ``array values auto break``() = formatSourceString false """ let arr = [|(1, 1,1); (1,2, 2); (1, 3, 3); (2, 1, 2); (2,2,4); (2, 3, 6); (3, 1, 3); (3, 2, 6); (3, 3, 9)|] - """ { config with SemicolonAtEndOfLine = false } + """ config |> should equal """ let arr = [| (1, 1, 1); (1, 2, 2); (1, 3, 3); (2, 1, 2); (2, 2, 4); (2, 3, 6); (3, 1, 3); (3, 2, 6); (3, 3, 9) |] @@ -316,47 +316,6 @@ let f1 = 4 """ -[] -let ``indentation issue 2 (core bug)``() = - formatSourceString false """ - f1 p1 " -" config - """ config - |> should equal """ - f1 p1 " -" config -""" - -[] -let ``indentation issue 3 (core bug)``() = - formatSourceString false """ -[] -let ``^a 1``() =() -[] -let ``b 2`` ()= () - """ config - |> should equal """ -[] -let ``^a 1``() = () -[] -let ``b 2``() = () -""" - -[] -let ``indentation issue 4``() = - formatSourceString false """ -type T = - static member f(p1, - p2) = - 1 - """ config - |> should equal """ -type T = - static member f (p1, - p2) = - 1 -""" - [] let ``semicolon issue 1``() = formatSourceString false """ @@ -411,24 +370,6 @@ D let x = 1 """ -[] -let ``core issue 1``() = - formatSourceString false """ -try - x := 1 -with -| :? E -> - y := f.ReadToEnd() -""" { config with PreserveEndOfLine = false } - |> prepend newline - |> should equal """ -try - x := 1 -with -| :? E -> - y := f.ReadToEnd() -""" - [] let ``records formatting without pEOL``() = formatSourceString false """