diff --git a/src/Fantomas.Tests/ColMultilineItemTests.fs b/src/Fantomas.Tests/ColMultilineItemTests.fs index f8e2ad0490..5f8a54f865 100644 --- a/src/Fantomas.Tests/ColMultilineItemTests.fs +++ b/src/Fantomas.Tests/ColMultilineItemTests.fs @@ -368,3 +368,41 @@ printFn () open Foo open Bar """ + +[] +let ``items should be collected from nested let-or-use in sequentials`` () = + formatSourceString + false + """ +let blah<'a> config : Type = +//#if DEBUG + failwith "" +//#endif + DoThing.doIt () + let result = Runner.Run<'a> config + () +""" + { config with + SpaceBeforeUppercaseInvocation = true + SpaceBeforeClassConstructor = true + SpaceBeforeMember = true + SpaceBeforeColon = true + SpaceBeforeSemicolon = true + MultilineBlockBracketsOnSameColumn = true + NewlineBetweenTypeDefinitionAndMembers = true + KeepIfThenInSameLine = true + AlignFunctionSignatureToIndentation = true + AlternativeLongMemberDefinitions = true + MultiLineLambdaClosingNewline = true } + |> prepend newline + |> should + equal + """ +let blah<'a> config : Type = + //#if DEBUG + failwith "" + //#endif + DoThing.doIt () + let result = Runner.Run<'a> config + () +""" diff --git a/src/Fantomas.Tests/KeepIndentInBranchTests.fs b/src/Fantomas.Tests/KeepIndentInBranchTests.fs index 698139bf65..39a5a1691f 100644 --- a/src/Fantomas.Tests/KeepIndentInBranchTests.fs +++ b/src/Fantomas.Tests/KeepIndentInBranchTests.fs @@ -1176,3 +1176,141 @@ and [] Bar<'context, 'a> = } } """ + +[] +let ``ifdef trivia should not influence outcome, 1646`` () = + formatSourceString + false + """ +module Foo = + [] + let blah<'a> config : Type = +#if DEBUG + failwith "" +#endif + DoThing.doIt () + let result = Runner.Run<'a> config + + if successful |> List.isEmpty then + result + else + + let errors = + unsuccessful + |> List.filter (fun report -> + not report.BuildResult.IsBuildSuccess + || not report.BuildResult.IsGenerateSuccess + ) + |> List.map (fun report -> report.BuildResult.ErrorMessage) + + failwith "" +""" + { config with + SpaceBeforeUppercaseInvocation = true + SpaceBeforeClassConstructor = true + SpaceBeforeMember = true + SpaceBeforeColon = true + SpaceBeforeSemicolon = true + MultilineBlockBracketsOnSameColumn = true + NewlineBetweenTypeDefinitionAndMembers = true + KeepIfThenInSameLine = true + AlignFunctionSignatureToIndentation = true + AlternativeLongMemberDefinitions = true + MultiLineLambdaClosingNewline = true + KeepIndentInBranch = true } + |> prepend newline + |> should + equal + """ +module Foo = + [] + let blah<'a> config : Type = +#if DEBUG + failwith "" +#endif + DoThing.doIt () + let result = Runner.Run<'a> config + + if successful |> List.isEmpty then + result + else + + let errors = + unsuccessful + |> List.filter (fun report -> + not report.BuildResult.IsBuildSuccess + || not report.BuildResult.IsGenerateSuccess + ) + |> List.map (fun report -> report.BuildResult.ErrorMessage) + + failwith "" +""" + +[] +let ``ifdef trivia should not influence outcome, idempotent`` () = + formatSourceString + false + """ +module Foo = + [] + let blah<'a> config : Type = +#if DEBUG + failwith "" +#endif + DoThing.doIt () + let result = Runner.Run<'a> config + + if successful |> List.isEmpty then + result + else + + let errors = + unsuccessful + |> List.filter (fun report -> + not report.BuildResult.IsBuildSuccess + || not report.BuildResult.IsGenerateSuccess + ) + |> List.map (fun report -> report.BuildResult.ErrorMessage) + + failwith "" +""" + { config with + SpaceBeforeUppercaseInvocation = true + SpaceBeforeClassConstructor = true + SpaceBeforeMember = true + SpaceBeforeColon = true + SpaceBeforeSemicolon = true + MultilineBlockBracketsOnSameColumn = true + NewlineBetweenTypeDefinitionAndMembers = true + KeepIfThenInSameLine = true + AlignFunctionSignatureToIndentation = true + AlternativeLongMemberDefinitions = true + MultiLineLambdaClosingNewline = true + KeepIndentInBranch = true } + |> prepend newline + |> should + equal + """ +module Foo = + [] + let blah<'a> config : Type = +#if DEBUG + failwith "" +#endif + DoThing.doIt () + let result = Runner.Run<'a> config + + if successful |> List.isEmpty then + result + else + + let errors = + unsuccessful + |> List.filter (fun report -> + not report.BuildResult.IsBuildSuccess + || not report.BuildResult.IsGenerateSuccess + ) + |> List.map (fun report -> report.BuildResult.ErrorMessage) + + failwith "" +""" diff --git a/src/Fantomas/CodePrinter.fs b/src/Fantomas/CodePrinter.fs index 7f1de62704..d11fbc98f6 100644 --- a/src/Fantomas/CodePrinter.fs +++ b/src/Fantomas/CodePrinter.fs @@ -1993,65 +1993,26 @@ and genExpr astContext synExpr ctx = && not (futureNlnCheck (genExpr astContext e) ctx) | _ -> false - let genInKeyword (binding: SynBinding) (ctx: Context) = - match inKeyWordTrivia binding ctx with - | Some (_, tn) -> - (printContentBefore tn - +> !- " in " - +> printContentAfter tn) - ctx - | None -> sepNone ctx - fun ctx -> if isInSameLine ctx then // short expression with in keyword // f.ex. let a in () atCurrentColumn (optSingle - (fun (p, x) -> + (fun (p, b) -> genLetBinding { astContext with IsFirstChild = p <> "and" } p - x - +> genInKeyword x) + b + +> genInKeyword b e) (List.tryHead bs) +> genExpr astContext e) ctx else - let letBindings (bs: (string * SynBinding) list) = - bs - |> List.map - (fun (p, x) -> - let expr = - enterNodeFor (synBindingToFsAstType x) x.RangeOfBindingAndRhs - +> genLetBinding - { astContext with - IsFirstChild = p <> "and" } - p - x - +> genInKeyword x - - let range = x.RangeOfBindingAndRhs - - let sepNln = - sepNlnConsideringTriviaContentBeforeForMainNode (synBindingToFsAstType x) range - - ColMultilineItem(expr, sepNln)) - - let rec synExpr e = - match e with - | LetOrUses (bs, e) -> letBindings bs @ synExpr e - | Sequentials s -> s |> List.collect synExpr - | _ -> - let t, r = synExprToFsAstType e - - [ ColMultilineItem( - genExpr astContext e, - sepNlnConsideringTriviaContentBeforeForMainNode t r - ) ] - - let items = letBindings bs @ synExpr e + let items = + collectMultilineItemForLetOrUses astContext bs e + @ collectMultilineItemForSynExpr astContext e atCurrentColumn (colWithNlnWhenItemIsMultilineUsingConfig items) ctx @@ -2117,20 +2078,7 @@ and genExpr astContext synExpr ctx = | SequentialSimple es | Sequentials es -> let items = - es - |> List.map - (fun e -> - let expr = genExpr astContext e - - let fsAstType, r = - match e with - | LetOrUses ((_, fb) :: _, _) -> (synBindingToFsAstType fb), fb.RangeOfBindingAndRhs - | _ -> synExprToFsAstType e - - let sepNln = - sepConsideringTriviaContentBeforeForMainNode sepSemiNln fsAstType r - - ColMultilineItem(expr, sepNln)) + List.collect (collectMultilineItemForSynExpr astContext) es atCurrentColumn (colWithNlnWhenItemIsMultilineUsingConfig items) @@ -3283,6 +3231,60 @@ and genAlternativeAppWithParenthesis app astContext = | Choice1Of2 t -> genAlternativeAppWithTupledArgument t astContext | Choice2Of2 s -> genAlternativeAppWithSingleParenthesisArgument s astContext +and collectMultilineItemForSynExpr (astContext: ASTContext) (e: SynExpr) : ColMultilineItem list = + match e with + | LetOrUses (bs, e) -> + collectMultilineItemForLetOrUses astContext bs e + @ collectMultilineItemForSynExpr astContext e + | Sequentials s -> + s + |> List.collect (collectMultilineItemForSynExpr astContext) + | _ -> + let t, r = synExprToFsAstType e + + [ ColMultilineItem(genExpr astContext e, sepNlnConsideringTriviaContentBeforeForMainNode t r) ] + +and collectMultilineItemForLetOrUses + (astContext: ASTContext) + (bs: (string * SynBinding) list) + (e: SynExpr) + : ColMultilineItem list = + bs + |> List.map + (fun (p, x) -> + let expr = + enterNodeFor (synBindingToFsAstType x) x.RangeOfBindingAndRhs + +> genLetBinding + { astContext with + IsFirstChild = p <> "and" } + p + x + +> genInKeyword x e + + let range = x.RangeOfBindingAndRhs + + let sepNln = + sepNlnConsideringTriviaContentBeforeForMainNode (synBindingToFsAstType x) range + + ColMultilineItem(expr, sepNln)) + +and genInKeyword (binding: SynBinding) (e: SynExpr) (ctx: Context) = + let inKeyWordTrivia (binding: SynBinding) = + let inRange = + ctx.MkRange binding.RangeOfBindingAndRhs.End e.Range.Start + + Map.tryFindOrEmptyList IN ctx.TriviaTokenNodes + |> TriviaHelpers.``keyword token after start column and on same line`` inRange + |> List.tryHead + + match inKeyWordTrivia binding with + | Some (_, tn) -> + (printContentBefore tn + +> !- " in " + +> printContentAfter tn) + ctx + | None -> sepNone ctx + and sepNlnBetweenTypeAndMembers (tdr: SynTypeDefnRepr) (ms: SynMemberDefn list) = match List.tryHead ms with | Some m ->