diff --git a/CHANGELOG.md b/CHANGELOG.md index aab8e3059..54bafec7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 6.3.12 - 2024-09-05 + +### Fixed +* Fantomas deletes attributes from getters. [#3114](https://github.com/fsprojects/fantomas/issues/3114) + ## 6.3.11 - 2024-08-16 ### Fixed diff --git a/src/Fantomas.Core.Tests/AttributeTests.fs b/src/Fantomas.Core.Tests/AttributeTests.fs index bb370b8a8..309a9d42a 100644 --- a/src/Fantomas.Core.Tests/AttributeTests.fs +++ b/src/Fantomas.Core.Tests/AttributeTests.fs @@ -1050,3 +1050,51 @@ let ``trivia in nested multiline tuple expression in attribute, 2525`` () = Justification = "Bytecode delta only")>] () """ + +[] +let ``attributes on member and get, set properties`` () = + formatSourceString + """ +type Object3D() = + [] + member this.position + with [] set v = _position <- v + and [] get () = _position +""" + config + |> prepend newline + |> should + equal + """ +type Object3D() = + [] + member this.position + with [] set v = _position <- v + and [] get () = _position +""" + +[] +let ``attributes on get,set properties, 3114`` () = + formatSourceString + """ +[] +type Object3D() = + let mutable _position: Vector3 = null + + member this.position + with [] set v = _position <- v + and [] get () = _position +""" + config + |> prepend newline + |> should + equal + """ +[] +type Object3D() = + let mutable _position: Vector3 = null + + member this.position + with [] set v = _position <- v + and [] get () = _position +""" diff --git a/src/Fantomas.Core/ASTTransformer.fs b/src/Fantomas.Core/ASTTransformer.fs index 69d4cb84a..5544c7022 100644 --- a/src/Fantomas.Core/ASTTransformer.fs +++ b/src/Fantomas.Core/ASTTransformer.fs @@ -2650,17 +2650,30 @@ let mkWithGetSet (withKeyword: range option) (getSet: GetSetKeywords option) = let mkPropertyGetSetBinding (creationAide: CreationAide) + (withOrAndKeyword: range) (accessibility: SynAccess option) (leadingKeyword: SingleTextNode) (binding: SynBinding) : PropertyGetSetBindingNode = match binding with | SynBinding( + attributes = attributes headPat = SynPat.LongIdent(extraId = Some extraIdent; argPats = SynArgPats.Pats ps) returnInfo = returnInfo expr = expr trivia = { EqualsRange = Some mEq InlineKeyword = inlineKw }) -> + // Attribute are not accurate in this case. + // The binding could contain attributes for the entire member and the getter or setter member. + // We use the `with` or `and` keyword to filter them. + let attributes = + attributes + |> List.map (fun al -> + { al with + Attributes = + al.Attributes + |> List.filter (fun a -> Position.posGt a.Range.Start withOrAndKeyword.End) }) + let e = parseExpressionInSynBinding returnInfo expr let returnTypeNode = mkBindingReturnInfo creationAide returnInfo @@ -2694,6 +2707,7 @@ let mkPropertyGetSetBinding PropertyGetSetBindingNode( Option.map (stn "inline") inlineKw, + mkAttributes creationAide attributes, mkSynAccess accessibility, leadingKeyword, pats, @@ -2863,7 +2877,7 @@ let mkMemberDefn (creationAide: CreationAide) (md: SynMemberDefn) = GetKeyword = Some getKeyword SetKeyword = Some setKeyword WithKeyword = withKeyword - AndKeyword = andKeyword }) -> + AndKeyword = Some andKeyword }) -> let firstAccessibility, firstBinding, firstKeyword, lastBinding, lastKeyword = if Position.posLt getKeyword.Start setKeyword.Start then @@ -2885,27 +2899,43 @@ let mkMemberDefn (creationAide: CreationAide) (md: SynMemberDefn) = | SynBinding(headPat = SynPat.LongIdent(accessibility = Some vis)) when rangeBeforePos memberName.Range vis.Range.Start -> - mkPropertyGetSetBinding creationAide (Some vis) firstKeyword firstBinding - | _ -> mkPropertyGetSetBinding creationAide None firstKeyword firstBinding + mkPropertyGetSetBinding creationAide withKeyword (Some vis) firstKeyword firstBinding + | _ -> mkPropertyGetSetBinding creationAide withKeyword None firstKeyword firstBinding let lastBinding = match lastBinding with | SynBinding(headPat = SynPat.LongIdent(accessibility = Some vis)) when rangeBeforePos memberName.Range vis.Range.Start -> - mkPropertyGetSetBinding creationAide (Some vis) lastKeyword lastBinding - | _ -> mkPropertyGetSetBinding creationAide None lastKeyword lastBinding + mkPropertyGetSetBinding creationAide andKeyword (Some vis) lastKeyword lastBinding + | _ -> mkPropertyGetSetBinding creationAide andKeyword None lastKeyword lastBinding + + // Attributes placed on the member will be included in both bindings for the getter and setter. + // We need to filter out the attributes above the leading keyword (typically `member`). + let memberAttributes = + ats + |> List.choose (fun al -> + let filteredAttributeList = + { al with + Attributes = + al.Attributes + |> List.filter (fun a -> Position.posLt a.Range.End lk.Range.Start) } + + if filteredAttributeList.Attributes.IsEmpty then + None + else + Some filteredAttributeList) MemberDefnPropertyGetSetNode( mkXmlDoc px, - mkAttributes creationAide ats, + mkAttributes creationAide memberAttributes, mkSynLeadingKeyword lk, Option.map (stn "inline") inlineKw, mkSynAccess accessibility, mkSynLongIdent memberName, stn "with" withKeyword, firstBinding, - Option.map (stn "and") andKeyword, + Some(stn "and" andKeyword), Some lastBinding, memberDefinitionRange ) @@ -2945,7 +2975,7 @@ let mkMemberDefn (creationAide: CreationAide) (md: SynMemberDefn) = match getKeyword, setKeyword with | Some getKeyword, None -> let bindingNode = - mkPropertyGetSetBinding creationAide visProperty (stn "get" getKeyword) binding + mkPropertyGetSetBinding creationAide withKeyword visProperty (stn "get" getKeyword) binding MemberDefnPropertyGetSetNode( mkXmlDoc px, @@ -2963,7 +2993,7 @@ let mkMemberDefn (creationAide: CreationAide) (md: SynMemberDefn) = |> MemberDefn.PropertyGetSet | None, Some setKeyword -> let bindingNode = - mkPropertyGetSetBinding creationAide visProperty (stn "set" setKeyword) binding + mkPropertyGetSetBinding creationAide withKeyword visProperty (stn "set" setKeyword) binding MemberDefnPropertyGetSetNode( mkXmlDoc px, diff --git a/src/Fantomas.Core/CodePrinter.fs b/src/Fantomas.Core/CodePrinter.fs index d78a975fd..47931e4e7 100644 --- a/src/Fantomas.Core/CodePrinter.fs +++ b/src/Fantomas.Core/CodePrinter.fs @@ -3877,6 +3877,7 @@ let genMemberDefn (md: MemberDefn) = | MemberDefn.PropertyGetSet node -> let genProperty (node: PropertyGetSetBindingNode) = genInlineOpt node.Inline + +> genOnelinerAttributes node.Attributes +> genAccessOpt node.Accessibility +> genSingleTextNode node.LeadingKeyword +> sepSpace diff --git a/src/Fantomas.Core/SyntaxOak.fs b/src/Fantomas.Core/SyntaxOak.fs index c13ecc169..5d2de5a89 100644 --- a/src/Fantomas.Core/SyntaxOak.fs +++ b/src/Fantomas.Core/SyntaxOak.fs @@ -2521,6 +2521,7 @@ type MemberDefnAbstractSlotNode type PropertyGetSetBindingNode ( inlineNode: SingleTextNode option, + attributes: MultipleAttributeListNode option, accessibility: SingleTextNode option, leadingKeyword: SingleTextNode, parameters: Pattern list, @@ -2533,6 +2534,7 @@ type PropertyGetSetBindingNode override val Children: Node array = [| yield! noa inlineNode + yield! noa attributes yield! noa accessibility yield leadingKeyword yield! List.map Pattern.Node parameters @@ -2541,6 +2543,7 @@ type PropertyGetSetBindingNode yield Expr.Node expr |] member val Inline = inlineNode + member val Attributes = attributes member val Accessibility = accessibility member val LeadingKeyword = leadingKeyword member val Parameters = parameters