@@ -27,15 +27,56 @@ public protocol FixItMessage: Sendable {
27
27
var fixItID : MessageID { get }
28
28
}
29
29
30
+ /// Types conforming to this protocol provide the data required for replacing a child node of a parent node.
31
+ ///
32
+ /// Conforming types should ensure the child of ``parent`` to be replaced at ``replacementRange`` is type-compatible
33
+ /// with ``newChild``. Conforming types are stored as type-erased existentials (i.e. `any ReplacingChildData`) in
34
+ /// ``FixIt/Change/replaceChild(data:)`` to keep ``FixIt`` type-erased.
35
+ public protocol ReplacingChildData : Sendable {
36
+ associatedtype Parent : SyntaxProtocol
37
+ associatedtype Child : SyntaxProtocol
38
+
39
+ /// The node whose child node at ``replacementRange`` to be replaced by ``newChild``.
40
+ var parent : Parent { get }
41
+
42
+ /// The node to replace the child node of ``parent`` at ``replacementRange``.
43
+ var newChild : Child { get }
44
+
45
+ /// The absolute position range of the child node to be replaced.
46
+ ///
47
+ /// If a nil child node is to be replaced, conforming types should provide a zero-length range with both bounds
48
+ /// denoting the start position of ``newChild`` in ``parent`` after replacement.
49
+ var replacementRange : Range < AbsolutePosition > { get }
50
+ }
51
+
30
52
/// A Fix-It that can be applied to resolve a diagnostic.
31
53
public struct FixIt : Sendable {
32
54
public enum Change : Sendable {
55
+ struct ReplacingOptionalChildData < Parent: SyntaxProtocol , Child: SyntaxProtocol > : ReplacingChildData {
56
+ let parent : Parent
57
+ let newChild : Child
58
+ let keyPath : WritableKeyPath < Parent , Child ? > & Sendable
59
+
60
+ var replacementRange : Range < AbsolutePosition > {
61
+ // need to upcast keyPath to strip Sendable for older Swift versions
62
+ let keyPath : WritableKeyPath < Parent , Child ? > = keyPath
63
+ if let oldChild = parent [ keyPath: keyPath] {
64
+ return oldChild. range
65
+ } else {
66
+ let newChild = parent. with ( keyPath, newChild) [ keyPath: keyPath] !
67
+ return newChild. position..< newChild. position
68
+ }
69
+ }
70
+ }
71
+
33
72
/// Replace `oldNode` by `newNode`.
34
73
case replace( oldNode: Syntax , newNode: Syntax )
35
74
/// Replace the leading trivia on the given token
36
75
case replaceLeadingTrivia( token: TokenSyntax , newTrivia: Trivia )
37
76
/// Replace the trailing trivia on the given token
38
77
case replaceTrailingTrivia( token: TokenSyntax , newTrivia: Trivia )
78
+ /// Replace the child node of the given parent node at the given replacement range with the given new child node
79
+ case replaceChild( data: any ReplacingChildData )
39
80
}
40
81
41
82
/// A description of what this Fix-It performs.
@@ -89,6 +130,12 @@ private extension FixIt.Change {
89
130
range: token. endPositionBeforeTrailingTrivia..< token. endPosition,
90
131
replacement: newTrivia. description
91
132
)
133
+
134
+ case . replaceChild( let replacingChildData) :
135
+ return SourceEdit (
136
+ range: replacingChildData. replacementRange,
137
+ replacement: replacingChildData. newChild. description
138
+ )
92
139
}
93
140
}
94
141
}
0 commit comments