diff --git a/CHANGELOG.md b/CHANGELOG.md index ea193d4427..df8091cc61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed * Indenting problem with `match` workaround for single-line stroustrup expressions [#2586](https://github.com/fsprojects/fantomas/issues/2586) +* System.Exception: Fantomas is trying to format the input multiple times due to the detect of multiple defines. [#628](https://github.com/fsprojects/fantomas/issues/628). ## [5.1.3] - 2022-11-14 diff --git a/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs b/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs index ec9c367d3d..8100275b88 100644 --- a/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs +++ b/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs @@ -2983,3 +2983,85 @@ let ``dead code in active block`` () = b #endif """ + +[] +let ``dont delete #if attr in "and" type, 628`` () = + formatSourceStringWithDefines + [ "NET2" ] + """ +#if NET2 +[] +#else +[] +#endif +[] +type internal T1 = T1 +and [] +#if NET2 +[] +#else +[] +#endif + internal T2 = T2 +""" + config + |> prepend newline + |> should + equal + """ +#if NET2 +[] +#else +#endif +[] +type internal T1 = T1 + +and [] +#if NET2 +[] +#else +#endif +internal T2 = T2 +""" + +[] +let ``attributes with directives in "and" type, 628`` () = + formatSourceString + false + """ +#if NET2 +[] +#else +[] +#endif +[] +type internal T1 = T1 +and [] +#if NET2 +[] +#else +[] +#endif + internal T2 = T2 +""" + config + |> prepend newline + |> should + equal + """ +#if NET2 +[] +#else +[] +#endif +[] +type internal T1 = T1 + +and [] +#if NET2 +[] +#else +[] +#endif +internal T2 = T2 +""" diff --git a/src/Fantomas.Core/CodePrinter.fs b/src/Fantomas.Core/CodePrinter.fs index 9890c98d1f..42da2b98c4 100644 --- a/src/Fantomas.Core/CodePrinter.fs +++ b/src/Fantomas.Core/CodePrinter.fs @@ -482,10 +482,14 @@ and genOnelinerAttributes ats = /// Try to group attributes if they are on the same line /// Separate same-line attributes by ';' /// Each bucket is printed in a different line -and genAttributes (ats: SynAttributes) = - colPost sepNlnUnlessLastEventIsNewline sepNln ats (fun a -> - (genAttributesCore a.Attributes |> genTriviaFor SynAttributeList_ a.Range) - +> sepNlnWhenWriteBeforeNewlineNotEmpty) +and genAttributes' nlnAfterOneliner (ats: SynAttributes) = + let addNln = nlnAfterOneliner || ats.Length > 1 + + colPost (ifElse addNln sepNlnUnlessLastEventIsNewline sepSpace) sepNln ats (fun a -> + let expr = genAttributesCore a.Attributes |> genTriviaFor SynAttributeList_ a.Range + expr) + +and genAttributes (ats: SynAttributes) = genAttributes' true ats and genPreXmlDoc (PreXmlDoc(lines, _)) = colPost sepNln sepNln lines (sprintf "///%s" >> (!-)) @@ -2916,7 +2920,7 @@ and genTypeDefn (TypeDef(ats, px, leadingKeyword, ao, tds, tcs, equalsRange, tdr let genLeadingKeyword = match leadingKeyword with | SynTypeDefnLeadingKeyword.Type mType -> genAttributes ats +> genTriviaFor SynTypeDefn_Type mType !- "type " - | SynTypeDefnLeadingKeyword.And mAnd -> genTriviaFor SynTypeDefn_And mAnd !- "and " +> genOnelinerAttributes ats + | SynTypeDefnLeadingKeyword.And mAnd -> genTriviaFor SynTypeDefn_And mAnd !- "and " +> genAttributes' false ats | SynTypeDefnLeadingKeyword.StaticType _ | SynTypeDefnLeadingKeyword.Synthetic -> sepNone diff --git a/src/Fantomas.Core/Context.fs b/src/Fantomas.Core/Context.fs index 77554bcb26..9d8738f093 100644 --- a/src/Fantomas.Core/Context.fs +++ b/src/Fantomas.Core/Context.fs @@ -1045,7 +1045,7 @@ let sepSemi (ctx: Context) = let ifAlignBrackets f g = ifElseCtx (fun ctx -> ctx.Config.MultilineBlockBracketsOnSameColumn) f g -let printTriviaContent (c: TriviaContent) (ctx: Context) = +let printTriviaContent (c: TriviaContent) addBefore (ctx: Context) = let currentLastLine = ctx.WriterModel.Lines |> List.tryHead // Some items like #if or Newline should be printed on a newline @@ -1073,11 +1073,18 @@ let printTriviaContent (c: TriviaContent) (ctx: Context) = +> ifElse after sepNlnForTrivia sepNone | Newline -> (ifElse addNewline (sepNlnForTrivia +> sepNlnForTrivia) sepNlnForTrivia) | Directive s - | Comment(CommentOnSingleLine s) -> (ifElse addNewline sepNlnForTrivia sepNone) +> !-s +> sepNlnForTrivia + | Comment(CommentOnSingleLine s) -> + (ifElse addNewline sepNlnForTrivia sepNone) + +> !-s + +> (ifElse addBefore sepNlnForTrivia sepNone) <| ctx let printTriviaInstructions (triviaInstructions: TriviaInstruction list) = - col sepNone triviaInstructions (fun { Trivia = trivia } -> printTriviaContent trivia.Item) + col + sepNone + triviaInstructions + (fun { Trivia = trivia + AddBefore = addBefore } -> printTriviaContent trivia.Item addBefore) let enterNodeFor (mainNodeName: FsAstType) (range: Range) (ctx: Context) = match Map.tryFind mainNodeName ctx.TriviaBefore with diff --git a/src/Fantomas.Core/Trivia.fs b/src/Fantomas.Core/Trivia.fs index 6d680f33d4..c4967b1e16 100644 --- a/src/Fantomas.Core/Trivia.fs +++ b/src/Fantomas.Core/Trivia.fs @@ -246,22 +246,41 @@ let triviaBeforeOrAfterEntireTree (rootNode: TriviaNode) (trivia: Trivia) : Triv AddBefore = isBefore } /// Try to put the trivia on top of the closest node +/// (or after closest node before in some special cases) /// If that didn't work put it after the last node let simpleTriviaToTriviaInstruction (containerNode: TriviaNode) (trivia: Trivia) : TriviaInstruction option = - containerNode.Children - |> Array.tryFind (fun node -> node.Range.StartLine > trivia.Range.StartLine) - |> Option.map (fun node -> + let childrenTypes = containerNode.Children |> Array.map (fun n -> n.Type) + + let mkTriviaNode addBefore (node: TriviaNode) = { Trivia = trivia Type = node.Type Range = node.Range - AddBefore = true }) + AddBefore = addBefore } + + let findChildNodeToAddAfter mainType prevType = + containerNode.Children + |> Array.indexed + |> Array.rev + |> Seq.tryFind (fun (i, node) -> + node.Type = mainType + && node.Range.StartLine < trivia.Range.StartLine + && childrenTypes[0 .. i - 1] |> Array.contains prevType) + |> Option.map snd + + let addAfterNode = + match trivia.Item with + | Directive _ -> + // link directive after attribute for "... and ..." type + findChildNodeToAddAfter FsAstType.SynAttributeList_ FsAstType.SynTypeDefn_And + | _ -> None + + addAfterNode + |> Option.map (mkTriviaNode false) |> Option.orElseWith (fun () -> - Array.tryLast containerNode.Children - |> Option.map (fun node -> - { Trivia = trivia - Type = node.Type - Range = node.Range - AddBefore = false })) + containerNode.Children + |> Array.tryFind (fun node -> node.Range.StartLine > trivia.Range.StartLine) + |> Option.map (mkTriviaNode true) + |> Option.orElseWith (fun () -> Array.tryLast containerNode.Children |> Option.map (mkTriviaNode false))) /// Try and find the smallest possible node let lineCommentAfterSourceCodeToTriviaInstruction