diff --git a/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs b/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs index 7d646d7176..3a31147190 100644 --- a/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs +++ b/src/Fantomas.Core.Tests/CompilerDirectivesTests.fs @@ -3233,3 +3233,82 @@ let CombineImportedAssembliesTask () """ + +[] +let ``should keep attributes on mutually recursive class, no defines`` () = + formatSourceStringWithDefines + [] + """ +type X = int + and +#if NET5_0_OR_GREATER + [] +#endif + [] + [] Y = int + +""" + config + |> prepend newline + |> should + equal + """ +type X = int +and +#if NET5_0_OR_GREATER +#endif + [] Y = int +""" + +[] +let ``should keep attributes on mutually recursive class, NET5_0_OR_GREATER`` () = + formatSourceStringWithDefines + [ "NET5_0_OR_GREATER" ] + """ +type X = int + and +#if NET5_0_OR_GREATER + [] +#endif + [] + [] Y = int + +""" + config + |> prepend newline + |> should + equal + """ +type X = int +and +#if NET5_0_OR_GREATER + [] +#endif + [] Y = int +""" + +[] +let ``should keep attributes on mutually recursive class, 3174`` () = + formatSourceString + """ +type X = int + and +#if NET5_0_OR_GREATER + [] +#endif + [] + [] Y = int + +""" + config + |> prepend newline + |> should + equal + """ +type X = int +and +#if NET5_0_OR_GREATER + [] +#endif + [] Y = int +""" diff --git a/src/Fantomas.Core/ASTTransformer.fs b/src/Fantomas.Core/ASTTransformer.fs index 58450725c3..2039691a55 100644 --- a/src/Fantomas.Core/ASTTransformer.fs +++ b/src/Fantomas.Core/ASTTransformer.fs @@ -2557,7 +2557,7 @@ let mkTypeDefn else match ats with | [] -> leadingKeyword.Range - | firstAttr :: _ -> firstAttr.Range + | firstAttr :: _ -> unionRanges leadingKeyword.Range firstAttr.Range let endRange = match trivia.EqualsRange with diff --git a/src/Fantomas.Core/CodePrinter.fs b/src/Fantomas.Core/CodePrinter.fs index 71b9ee65a5..de9490919d 100644 --- a/src/Fantomas.Core/CodePrinter.fs +++ b/src/Fantomas.Core/CodePrinter.fs @@ -299,6 +299,40 @@ let genOnelinerAttributes (n: MultipleAttributeListNode option) = ifElse ats.IsEmpty sepNone (genAttrs +> sepSpace) +let partitionOn splitBefore splitAfter items = + let folder acc item = + match acc with + | [] -> [ [ item ] ] + | (lastItem :: _ as currentGroup) :: restGroups when splitAfter lastItem -> + [ item ] :: currentGroup :: restGroups + | currentGroup :: restGroups when splitBefore item -> [ item ] :: currentGroup :: restGroups + | currentGroup :: restGroups -> (item :: currentGroup) :: restGroups + + items |> List.fold folder [] |> List.map List.rev |> List.rev + +let genCompactedAttributes (n: MultipleAttributeListNode option) = + match n with + | None -> sepNone + | Some n -> + let attributeLists = + n.AttributeLists |> partitionOn (_.HasContentBefore) (_.HasContentAfter) + + col sepNone attributeLists (fun (al) -> + let ats = al |> List.collect _.Attributes + + let openingToken = + List.tryHead al |> Option.map (fun (a: AttributeListNode) -> a.Opening) + + let closingToken = + List.tryLast al |> Option.map (fun (a: AttributeListNode) -> a.Closing) + + optSingle genSingleTextNode openingToken + +> (genAttributesCore ats) + +> optSingle genSingleTextNode closingToken + +> sepNlnWhenWriteBeforeNewlineNotEmpty + |> genNode (al |> List.head)) + |> genNode n + let genAttributes (node: MultipleAttributeListNode option) = match node with | None -> sepNone @@ -3455,6 +3489,7 @@ let hasTriviaAfterLeadingKeyword (identifier: IdentListNode) (accessibility: Sin | _ -> false let beforeIdentifier = identifier.HasContentBefore + beforeAccess || beforeIdentifier let genTypeDefn (td: TypeDefn) = @@ -3469,16 +3504,28 @@ let genTypeDefn (td: TypeDefn) = let hasTriviaAfterLeadingKeyword = hasTriviaAfterLeadingKeyword typeName.Identifier typeName.Accessibility + let hasTriviaInAttributes = + match typeName.Attributes with + | Some attributes -> + attributes.HasContentBefore + || attributes.HasContentAfter + || attributes.AttributeLists + |> List.exists (fun a -> a.HasContentBefore || a.HasContentAfter) + | None -> false + + let shouldIndent = + hasTriviaAfterLeadingKeyword || (hasAndKeyword && hasTriviaInAttributes) + genXml typeName.XmlDoc +> onlyIfNot hasAndKeyword (genAttributes typeName.Attributes) +> genSingleTextNode typeName.LeadingKeyword - +> onlyIf hasTriviaAfterLeadingKeyword indent - +> onlyIf hasAndKeyword (sepSpace +> genOnelinerAttributes typeName.Attributes) + +> onlyIf shouldIndent indent + +> onlyIf hasAndKeyword (sepSpace +> genCompactedAttributes typeName.Attributes) +> sepSpace +> genAccessOpt typeName.Accessibility +> genTypeAndParam (genIdentListNode typeName.Identifier) typeName.TypeParameters +> onlyIfNot typeName.Constraints.IsEmpty (sepSpace +> genTypeConstraints typeName.Constraints) - +> onlyIf hasTriviaAfterLeadingKeyword unindent + +> onlyIf shouldIndent unindent +> leadingExpressionIsMultiline (optSingle (fun imCtor -> sepSpaceBeforeClassConstructor +> genImplicitConstructor imCtor)