@@ -69,20 +69,8 @@ import SwiftSyntaxBuilder
69
69
/// ```swift
70
70
/// anything here
71
71
/// ```
72
- public struct ExpandEditorPlaceholder : EditRefactoringProvider {
73
- public static func isPlaceholder( _ str: String ) -> Bool {
74
- return str. hasPrefix ( placeholderStart) && str. hasSuffix ( placeholderEnd)
75
- }
76
-
77
- public static func wrapInPlaceholder( _ str: String ) -> String {
78
- return placeholderStart + str + placeholderEnd
79
- }
80
-
81
- public static func wrapInTypePlaceholder( _ str: String , type: String ) -> String {
82
- return Self . wrapInPlaceholder ( " T## " + str + " ## " + type)
83
- }
84
-
85
- public static func textRefactor( syntax token: TokenSyntax , in context: Void ) -> [ SourceEdit ] {
72
+ struct ExpandSingleEditorPlaceholder : EditRefactoringProvider {
73
+ static func textRefactor( syntax token: TokenSyntax , in context: Void ) -> [ SourceEdit ] {
86
74
guard let placeholder = EditorPlaceholderData ( token: token) else {
87
75
return [ ]
88
76
}
@@ -132,28 +120,68 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
132
120
/// }
133
121
/// ```
134
122
///
135
- /// Expansion on `closure1` and `normalArg` is the same as `ExpandEditorPlaceholder `.
136
- public struct ExpandEditorPlaceholders : EditRefactoringProvider {
123
+ /// Expansion on `closure1` and `normalArg` is the same as `ExpandSingleEditorPlaceholder `.
124
+ public struct ExpandEditorPlaceholder : EditRefactoringProvider {
137
125
public static func textRefactor( syntax token: TokenSyntax , in context: Void ) -> [ SourceEdit ] {
138
126
guard let placeholder = token. parent? . as ( DeclReferenceExprSyntax . self) ,
139
127
placeholder. baseName. isEditorPlaceholder,
140
128
let arg = placeholder. parent? . as ( LabeledExprSyntax . self) ,
141
129
let argList = arg. parent? . as ( LabeledExprListSyntax . self) ,
142
- let call = argList. parent? . as ( FunctionCallExprSyntax . self)
130
+ let call = argList. parent? . as ( FunctionCallExprSyntax . self) ,
131
+ let expandedTrailingClosures = ExpandEditorPlaceholdersToTrailingClosures . expandTrailingClosurePlaceholders ( in: call, ifIncluded: arg)
143
132
else {
144
- return ExpandEditorPlaceholder . textRefactor ( syntax: token)
133
+ return ExpandSingleEditorPlaceholder . textRefactor ( syntax: token)
145
134
}
146
135
136
+ return [ SourceEdit . replace ( call, with: expandedTrailingClosures. description) ]
137
+ }
138
+ }
139
+
140
+ /// Expand all the editor placeholders in the function call that can be converted to trailing closures.
141
+ ///
142
+ /// ## Before
143
+ /// ```swift
144
+ /// foo(
145
+ /// arg: <#T##Int#>,
146
+ /// firstClosure: <#T##(Int) -> String##(Int) -> String##(_ someInt: Int) -> String#>,
147
+ /// secondClosure: <#T##(Int) -> String##(Int) -> String##(_ someInt: Int) -> String#>
148
+ /// )
149
+ /// ```
150
+ ///
151
+ /// ## Expansion of `foo`
152
+ /// ```swift
153
+ /// foo(
154
+ /// arg: <#T##Int#>,
155
+ /// ) { someInt in
156
+ /// <#T##String#>
157
+ /// } secondClosure: { someInt in
158
+ /// <#T##String#>
159
+ /// }
160
+ /// ```
161
+ public struct ExpandEditorPlaceholdersToTrailingClosures : SyntaxRefactoringProvider {
162
+ public static func refactor( syntax call: FunctionCallExprSyntax , in context: Void = ( ) ) -> FunctionCallExprSyntax ? {
163
+ return Self . expandTrailingClosurePlaceholders ( in: call, ifIncluded: nil )
164
+ }
165
+
166
+ /// If the given argument is `nil` or one of the last arguments that are all
167
+ /// function-typed placeholders and this call doesn't have a trailing
168
+ /// closure, then return a replacement of this call with one that uses
169
+ /// closures based on the function types provided by each editor placeholder.
170
+ /// Otherwise return nil.
171
+ fileprivate static func expandTrailingClosurePlaceholders(
172
+ in call: FunctionCallExprSyntax ,
173
+ ifIncluded arg: LabeledExprSyntax ?
174
+ ) -> FunctionCallExprSyntax ? {
147
175
guard let expanded = call. expandTrailingClosurePlaceholders ( ifIncluded: arg) else {
148
- return ExpandEditorPlaceholder . textRefactor ( syntax : token )
176
+ return nil
149
177
}
150
178
151
- let callToTrailingContext = CallToTrailingClosures . Context ( startAtArgument: argList . count - expanded. numClosures)
179
+ let callToTrailingContext = CallToTrailingClosures . Context ( startAtArgument: call . arguments . count - expanded. numClosures)
152
180
guard let trailing = CallToTrailingClosures . refactor ( syntax: expanded. expr, in: callToTrailingContext) else {
153
- return ExpandEditorPlaceholder . textRefactor ( syntax : token )
181
+ return nil
154
182
}
155
183
156
- return [ SourceEdit . replace ( call , with : trailing. description ) ]
184
+ return trailing
157
185
}
158
186
}
159
187
@@ -186,9 +214,9 @@ extension FunctionTypeSyntax {
186
214
let ret = returnClause. type. description
187
215
let placeholder : String
188
216
if ret == " Void " || ret == " () " {
189
- placeholder = ExpandEditorPlaceholder . wrapInTypePlaceholder ( " code " , type: " Void " )
217
+ placeholder = wrapInTypePlaceholder ( " code " , type: " Void " )
190
218
} else {
191
- placeholder = ExpandEditorPlaceholder . wrapInTypePlaceholder ( ret, type: ret)
219
+ placeholder = wrapInTypePlaceholder ( ret, type: ret)
192
220
}
193
221
194
222
let statementPlaceholder = DeclReferenceExprSyntax (
@@ -221,17 +249,19 @@ extension TupleTypeElementSyntax {
221
249
return firstName
222
250
}
223
251
224
- return . identifier( ExpandEditorPlaceholder . wrapInPlaceholder ( type. description) )
252
+ return . identifier( wrapInPlaceholder ( type. description) )
225
253
}
226
254
}
227
255
228
256
extension FunctionCallExprSyntax {
229
- /// If the given argument is one of the last arguments that are all
257
+ /// If the given argument is `nil` or one of the last arguments that are all
230
258
/// function-typed placeholders and this call doesn't have a trailing
231
259
/// closure, then return a replacement of this call with one that uses
232
260
/// closures based on the function types provided by each editor placeholder.
233
261
/// Otherwise return nil.
234
- fileprivate func expandTrailingClosurePlaceholders( ifIncluded: LabeledExprSyntax ) -> ( expr: FunctionCallExprSyntax , numClosures: Int ) ? {
262
+ fileprivate func expandTrailingClosurePlaceholders(
263
+ ifIncluded: LabeledExprSyntax ?
264
+ ) -> ( expr: FunctionCallExprSyntax , numClosures: Int ) ? {
235
265
var includedArg = false
236
266
var argsToExpand = 0
237
267
for arg in arguments. reversed ( ) {
@@ -249,13 +279,13 @@ extension FunctionCallExprSyntax {
249
279
argsToExpand += 1
250
280
}
251
281
252
- guard includedArg else {
282
+ guard includedArg || ifIncluded == nil else {
253
283
return nil
254
284
}
255
285
256
286
var expandedArgs = [ LabeledExprSyntax] ( )
257
287
for arg in arguments. suffix ( argsToExpand) {
258
- let edits = ExpandEditorPlaceholder . textRefactor ( syntax: arg. expression. cast ( DeclReferenceExprSyntax . self) . baseName)
288
+ let edits = ExpandSingleEditorPlaceholder . textRefactor ( syntax: arg. expression. cast ( DeclReferenceExprSyntax . self) . baseName)
259
289
guard edits. count == 1 , let edit = edits. first, !edit. replacement. isEmpty else {
260
290
return nil
261
291
}
@@ -291,7 +321,7 @@ fileprivate enum EditorPlaceholderData {
291
321
case typed( text: Substring , type: TypeSyntax )
292
322
293
323
init ? ( token: TokenSyntax ) {
294
- guard ExpandEditorPlaceholder . isPlaceholder ( token. text) else {
324
+ guard isPlaceholder ( token. text) else {
295
325
return nil
296
326
}
297
327
@@ -346,6 +376,21 @@ fileprivate enum EditorPlaceholderData {
346
376
}
347
377
}
348
378
379
+ @_spi ( Testing)
380
+ public func isPlaceholder( _ str: String ) -> Bool {
381
+ return str. hasPrefix ( placeholderStart) && str. hasSuffix ( placeholderEnd)
382
+ }
383
+
384
+ @_spi ( Testing)
385
+ public func wrapInPlaceholder( _ str: String ) -> String {
386
+ return placeholderStart + str + placeholderEnd
387
+ }
388
+
389
+ @_spi ( Testing)
390
+ public func wrapInTypePlaceholder( _ str: String , type: String ) -> String {
391
+ return wrapInPlaceholder ( " T## " + str + " ## " + type)
392
+ }
393
+
349
394
/// Split the given string into two components on the first instance of
350
395
/// `separatedBy`. The second element is empty if `separatedBy` is missing
351
396
/// from the initial string.
0 commit comments