12
12
13
13
import SwiftSyntax
14
14
15
- /// A type that transforms syntax to provide a (context-sensitive)
16
- /// refactoring.
17
- ///
18
- /// A type conforming to the `RefactoringProvider` protocol defines
19
- /// a refactoring action against a family of Swift syntax trees.
20
- ///
21
- /// Refactoring
22
- /// ===========
23
- ///
24
- /// Refactoring is the act of transforming source code to be more effective.
25
- /// A refactoring does not affect the semantics of code it is transforming.
26
- /// Rather, it makes that code easier to read and reason about.
27
- ///
28
- /// Code Transformation
29
- /// ===================
30
- ///
31
- /// Refactoring is expressed as structural transformations of Swift
32
- /// syntax trees. The SwiftSyntax API provides a natural, easy-to-use,
33
- /// and compositional set of updates to the syntax tree. For example, a
34
- /// refactoring action that wishes to exchange the leading trivia of a node
35
- /// would call `with(\.leadingTrivia, _:)` against its input syntax and return
36
- /// the resulting syntax node. For compound syntax nodes, entire sub-trees
37
- /// can be added, exchanged, or removed by calling the corresponding `with`
38
- /// API.
15
+ /// A refactoring expressed as textual edits on the original syntax tree. In
16
+ /// general clients should prefer `SyntaxRefactoringProvider` where possible.
17
+ public protocol EditRefactoringProvider {
18
+ /// The type of syntax this refactoring action accepts.
19
+ associatedtype Input : SyntaxProtocol = SourceFileSyntax
20
+ /// Contextual information used by the refactoring action.
21
+ associatedtype Context = Void
22
+
23
+ /// Perform the refactoring action on the provided syntax node.
24
+ ///
25
+ /// - Parameters:
26
+ /// - syntax: The syntax to transform.
27
+ /// - context: Contextual information used by the refactoring action.
28
+ /// - Returns: Textual edits that describe how to apply the result of the
29
+ /// refactoring action on locations within the original tree. An
30
+ /// empty array if the refactoring could not be performed.
31
+ static func textRefactor( syntax: Self . Input , in context: Self . Context ) -> [ Edit ]
32
+ }
33
+
34
+ extension EditRefactoringProvider where Context == Void {
35
+ /// See `textRefactor(syntax:in:)`. This method provides a convenient way to
36
+ /// invoke a refactoring action that requires no context.
37
+ ///
38
+ /// - Parameters:
39
+ /// - syntax: The syntax to transform.
40
+ /// - Returns: Textual edits describing the refactoring to perform.
41
+ public static func textRefactor( syntax: Self . Input ) -> [ Edit ] {
42
+ return self . textRefactor ( syntax: syntax, in: ( ) )
43
+ }
44
+ }
45
+
46
+ /// A refactoring expressed as a structural transformation of the original
47
+ /// syntax node. For example, a refactoring action that wishes to exchange the
48
+ /// leading trivia of a node could call call `with(\.leadingTrivia, _:)`
49
+ /// against its input syntax and return the resulting syntax node. Or, for
50
+ /// compound syntax nodes, entire sub-trees can be added, exchanged, or removed
51
+ /// by calling the corresponding `with` API.
39
52
///
40
53
/// - Note: The syntax trees returned by SwiftSyntax are immutable: any
41
54
/// transformation made against the tree results in a distinct tree.
@@ -44,23 +57,24 @@ import SwiftSyntax
44
57
/// =========================
45
58
///
46
59
/// A refactoring provider cannot assume that the syntax it is given is
47
- /// neessarily well-formed. As the SwiftSyntax library is capable of recovering
60
+ /// necessarily well-formed. As the SwiftSyntax library is capable of recovering
48
61
/// from a variety of erroneous inputs, a refactoring provider has to be
49
62
/// prepared to fail gracefully as well. Many refactoring providers follow a
50
63
/// common validation pattern that "preflights" the refactoring by testing the
51
64
/// structure of the provided syntax nodes. If the tests fail, the refactoring
52
- /// provider exits early by returning `nil` . It is recommended that refactoring
53
- /// actions fail as quickly as possible to give any associated tooling
54
- /// space to recover as well.
55
- public protocol RefactoringProvider {
65
+ /// provider exits early by returning an empty array . It is recommended that
66
+ /// refactoring actions fail as quickly as possible to give any associated
67
+ /// tooling space to recover as well.
68
+ public protocol SyntaxRefactoringProvider : EditRefactoringProvider {
56
69
/// The type of syntax this refactoring action accepts.
57
70
associatedtype Input : SyntaxProtocol = SourceFileSyntax
58
71
/// The type of syntax this refactoring action returns.
59
72
associatedtype Output : SyntaxProtocol = SourceFileSyntax
60
73
/// Contextual information used by the refactoring action.
61
74
associatedtype Context = Void
62
75
63
- /// Perform the refactoring action on the provided syntax node.
76
+ /// Perform the refactoring action on the provided syntax node. It is assumed
77
+ /// that the returned output completely replaces the input node.
64
78
///
65
79
/// - Parameters:
66
80
/// - syntax: The syntax to transform.
@@ -70,11 +84,9 @@ public protocol RefactoringProvider {
70
84
static func refactor( syntax: Self . Input , in context: Self . Context ) -> Self . Output ?
71
85
}
72
86
73
- extension RefactoringProvider where Context == Void {
74
- /// Perform the refactoring action on the provided syntax node.
75
- ///
76
- /// This method provides a convenient way to invoke a refactoring action that
77
- /// requires no context.
87
+ extension SyntaxRefactoringProvider where Context == Void {
88
+ /// See `refactor(syntax:in:)`. This method provides a convenient way to
89
+ /// invoke a refactoring action that requires no context.
78
90
///
79
91
/// - Parameters:
80
92
/// - syntax: The syntax to transform.
@@ -84,3 +96,80 @@ extension RefactoringProvider where Context == Void {
84
96
return self . refactor ( syntax: syntax, in: ( ) )
85
97
}
86
98
}
99
+
100
+ extension SyntaxRefactoringProvider {
101
+ /// Provides a default implementation for
102
+ /// `EditRefactoringProvider.textRefactor(syntax:in:)` that produces an edit
103
+ /// to replace the input of `refactor(syntax:in:)` with its returned output.
104
+ public static func textRefactor( syntax: Self . Input , in context: Self . Context ) -> [ Edit ] {
105
+ guard let output = refactor ( syntax: syntax, in: context) else {
106
+ return [ ]
107
+ }
108
+ return [ Edit . replace ( syntax, with: output. description) ]
109
+ }
110
+ }
111
+
112
+ /// An textual edit to the original source represented by a range and a
113
+ /// replacement.
114
+ public struct Edit : Equatable {
115
+ /// The location in the original source that this edit starts at.
116
+ public let start : AbsolutePosition
117
+ /// The (closed) end of this edit in the original source. Equal to the start
118
+ /// for an addition.
119
+ public let end : AbsolutePosition
120
+ /// The text to replace the original range with. Empty for a deletion.
121
+ public let replacement : String
122
+
123
+ /// Length of the original source range that this edit applies to. Zero if
124
+ /// this is an addition.
125
+ public var length : SourceLength {
126
+ return SourceLength ( utf8Length: end. utf8Offset - start. utf8Offset)
127
+ }
128
+
129
+ /// Create an edit to replace `start` to (closed) `end` in the original
130
+ /// source with `replacement`.
131
+ public init ( start: AbsolutePosition , end: AbsolutePosition , replacement: String ) {
132
+ self . start = start
133
+ self . end = end
134
+ self . replacement = replacement
135
+ }
136
+
137
+ /// Convenience function to create a textual addition after the given node
138
+ /// and its trivia.
139
+ public static func insert< T: SyntaxProtocol > ( _ newText: String , after node: T ) -> Edit {
140
+ return Edit ( start: node. endPosition, end: node. endPosition, replacement: newText)
141
+ }
142
+
143
+ /// Convenience function to create a textual addition before the given node
144
+ /// and its trivia.
145
+ public static func insert< T: SyntaxProtocol > ( _ newText: String , before node: T ) -> Edit {
146
+ return Edit ( start: node. position, end: node. position, replacement: newText)
147
+ }
148
+
149
+ /// Convenience function to create a textual replacement of the given node,
150
+ /// including its trivia.
151
+ public static func replace< T: SyntaxProtocol > ( _ node: T , with replacement: String ) -> Edit {
152
+ return Edit ( start: node. position, end: node. endPosition, replacement: replacement)
153
+ }
154
+
155
+ /// Convenience function to create a textual deletion the given node and its
156
+ /// trivia.
157
+ public static func remove< T: SyntaxProtocol > ( _ node: T ) -> Edit {
158
+ return Edit ( start: node. position, end: node. endPosition, replacement: " " )
159
+ }
160
+ }
161
+
162
+ extension Edit : CustomStringConvertible {
163
+ public var description : String {
164
+ let hasNewline = replacement. contains { $0. isNewline }
165
+ if hasNewline {
166
+ return #"""
167
+ \#( start. utf8Offset) - \#( end. utf8Offset)
168
+ """
169
+ \#( replacement)
170
+ """
171
+ """#
172
+ }
173
+ return " \( start. utf8Offset) - \( end. utf8Offset) \" \( replacement) \" "
174
+ }
175
+ }
0 commit comments