From 851ee738191b14fa810bf5f73676fee069f448c2 Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 28 Aug 2020 16:13:58 +0200 Subject: [PATCH] Validate formatted code after unit tests. Fixes #842. --- src/Fantomas.Tests/ControlStructureTests.fs | 16 ++++---- src/Fantomas.Tests/TestHelpers.fs | 45 +++++++++++++++++++-- src/Fantomas/CodePrinter.fs | 8 ++-- src/Fantomas/Utils.fs | 6 ++- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/Fantomas.Tests/ControlStructureTests.fs b/src/Fantomas.Tests/ControlStructureTests.fs index f60418b95f..61e43d29d7 100644 --- a/src/Fantomas.Tests/ControlStructureTests.fs +++ b/src/Fantomas.Tests/ControlStructureTests.fs @@ -216,10 +216,9 @@ let x = |> prepend newline |> should equal """ let x = - if try - true - with Failure _ -> false - then + if (try + true + with Failure _ -> false) then () else () @@ -239,11 +238,10 @@ let y = |> prepend newline |> should equal """ let y = - if try - true - finally - false - then + if (try + true + finally + false) then () else () diff --git a/src/Fantomas.Tests/TestHelpers.fs b/src/Fantomas.Tests/TestHelpers.fs index 37b75030b1..f82991f128 100644 --- a/src/Fantomas.Tests/TestHelpers.fs +++ b/src/Fantomas.Tests/TestHelpers.fs @@ -18,15 +18,54 @@ let newline = "\n" let sharedChecker = lazy(FSharpChecker.Create()) +let private safeToIgnoreWarnings = + [ "This construct is deprecated: it is only for use in the F# library" + "Identifiers containing '@' are reserved for use in F# code generation" ] + +let private isValidAndHasNoWarnings fileName source parsingOptions = + let allDefineOptions = + TokenParser.getOptimizedDefinesSets source + @ (TokenParser.getDefines source |> List.map List.singleton) + @ [[]] + |> List.distinct + + allDefineOptions + |> List.map (fun conditionalCompilationDefines -> + async { + let parsingOptionsWithDefines = { parsingOptions with ConditionalCompilationDefines = conditionalCompilationDefines } // IsInteractive = false + // Run the first phase (untyped parsing) of the compiler + let sourceText = FSharp.Compiler.Text.SourceText.ofString source + let! untypedRes = sharedChecker.Value.ParseFile(fileName, sourceText, parsingOptionsWithDefines) + + let errors = + untypedRes.Errors + |> Array.filter (fun e -> not (List.contains e.Message safeToIgnoreWarnings)) + // FSharpErrorInfo contains both Errors and Warnings + // https://fsharp.github.io/FSharp.Compiler.Service/reference/fsharp-compiler-sourcecodeservices-fsharperrorinfo.html + return Array.isEmpty errors + } + ) + |> Async.Parallel + |> Async.map (fun results -> Seq.fold (&&) true results) + let formatSourceString isFsiFile (s : string) config = // On Linux/Mac this will exercise different line endings let s = s.Replace("\r\n", Environment.NewLine) let fileName = if isFsiFile then "/src.fsi" else "/src.fsx" + let parsingOptions = FakeHelpers.createParsingOptionsFromFile fileName + + async { + let! formatted = + CodeFormatter.FormatDocumentAsync(fileName, SourceOrigin.SourceString s, config, parsingOptions, sharedChecker.Value) + + let! isValid = isValidAndHasNoWarnings fileName formatted parsingOptions + 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") + } - CodeFormatter.FormatDocumentAsync(fileName, SourceOrigin.SourceString s, config, - FakeHelpers.createParsingOptionsFromFile fileName, sharedChecker.Value) |> Async.RunSynchronously - |> fun s -> s.Replace("\r\n", "\n") let formatSourceStringWithDefines defines (s : string) config = // On Linux/Mac this will exercise different line endings diff --git a/src/Fantomas/CodePrinter.fs b/src/Fantomas/CodePrinter.fs index 7fbf71e743..e6d516b68a 100644 --- a/src/Fantomas/CodePrinter.fs +++ b/src/Fantomas/CodePrinter.fs @@ -1965,12 +1965,12 @@ and genExpr astContext synExpr = // x // bool expr x should be indented +> ifElse hasCommentAfterIfKeyword (indent +> sepNln) sepNone - +> genExpr astContext e1 - +> ifElse hasCommentAfterBoolExpr sepNln sepSpace +> (match e1 with | SynExpr.TryWith _ - | SynExpr.TryFinally _ -> atCurrentColumn (sepNln +> genThen synExpr.Range) - | _ -> genThen synExpr.Range) + | SynExpr.TryFinally _ -> sepOpenT +> genExpr astContext e1 +> sepCloseT + | _ -> genExpr astContext e1) + +> ifElse hasCommentAfterBoolExpr sepNln sepSpace + +> genThen synExpr.Range // f.ex if x then // meh // 0 // 0 should be indented diff --git a/src/Fantomas/Utils.fs b/src/Fantomas/Utils.fs index 031f3df4e7..be9497ce17 100644 --- a/src/Fantomas/Utils.fs +++ b/src/Fantomas/Utils.fs @@ -130,4 +130,8 @@ module Map = | None -> defaultValue let tryFindOrEmptyList (key:'t) (map: Map<'t, 'g list>) = - tryFindOrDefault [] key map \ No newline at end of file + tryFindOrDefault [] key map + +module Async = + let map f computation = + async.Bind(computation, f >> async.Return) \ No newline at end of file