Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate formatted code after unit tests #1048

Merged
merged 2 commits into from
Aug 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions src/Fantomas.Tests/ControlStructureTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
()
Expand All @@ -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
()
Expand Down
45 changes: 42 additions & 3 deletions src/Fantomas.Tests/TestHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/Fantomas/CodePrinter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1970,12 +1970,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
Expand Down
6 changes: 5 additions & 1 deletion src/Fantomas/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,8 @@ module Map =
| None -> defaultValue

let tryFindOrEmptyList (key:'t) (map: Map<'t, 'g list>) =
tryFindOrDefault [] key map
tryFindOrDefault [] key map

module Async =
let map f computation =
async.Bind(computation, f >> async.Return)