@@ -69,20 +69,8 @@ import SwiftSyntaxBuilder
6969/// ```swift
7070/// anything here
7171/// ```
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 ] {
8674 guard let placeholder = EditorPlaceholderData ( token: token) else {
8775 return [ ]
8876 }
@@ -132,28 +120,68 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
132120/// }
133121/// ```
134122///
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 {
137125 public static func textRefactor( syntax token: TokenSyntax , in context: Void ) -> [ SourceEdit ] {
138126 guard let placeholder = token. parent? . as ( DeclReferenceExprSyntax . self) ,
139127 placeholder. baseName. isEditorPlaceholder,
140128 let arg = placeholder. parent? . as ( LabeledExprSyntax . self) ,
141129 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)
143132 else {
144- return ExpandEditorPlaceholder . textRefactor ( syntax: token)
133+ return ExpandSingleEditorPlaceholder . textRefactor ( syntax: token)
145134 }
146135
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 ? {
147175 guard let expanded = call. expandTrailingClosurePlaceholders ( ifIncluded: arg) else {
148- return ExpandEditorPlaceholder . textRefactor ( syntax : token )
176+ return nil
149177 }
150178
151- let callToTrailingContext = CallToTrailingClosures . Context ( startAtArgument: argList . count - expanded. numClosures)
179+ let callToTrailingContext = CallToTrailingClosures . Context ( startAtArgument: call . arguments . count - expanded. numClosures)
152180 guard let trailing = CallToTrailingClosures . refactor ( syntax: expanded. expr, in: callToTrailingContext) else {
153- return ExpandEditorPlaceholder . textRefactor ( syntax : token )
181+ return nil
154182 }
155183
156- return [ SourceEdit . replace ( call , with : trailing. description ) ]
184+ return trailing
157185 }
158186}
159187
@@ -186,9 +214,9 @@ extension FunctionTypeSyntax {
186214 let ret = returnClause. type. description
187215 let placeholder : String
188216 if ret == " Void " || ret == " () " {
189- placeholder = ExpandEditorPlaceholder . wrapInTypePlaceholder ( " code " , type: " Void " )
217+ placeholder = wrapInTypePlaceholder ( " code " , type: " Void " )
190218 } else {
191- placeholder = ExpandEditorPlaceholder . wrapInTypePlaceholder ( ret, type: ret)
219+ placeholder = wrapInTypePlaceholder ( ret, type: ret)
192220 }
193221
194222 let statementPlaceholder = DeclReferenceExprSyntax (
@@ -221,17 +249,19 @@ extension TupleTypeElementSyntax {
221249 return firstName
222250 }
223251
224- return . identifier( ExpandEditorPlaceholder . wrapInPlaceholder ( type. description) )
252+ return . identifier( wrapInPlaceholder ( type. description) )
225253 }
226254}
227255
228256extension 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
230258 /// function-typed placeholders and this call doesn't have a trailing
231259 /// closure, then return a replacement of this call with one that uses
232260 /// closures based on the function types provided by each editor placeholder.
233261 /// 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 ) ? {
235265 var includedArg = false
236266 var argsToExpand = 0
237267 for arg in arguments. reversed ( ) {
@@ -249,13 +279,13 @@ extension FunctionCallExprSyntax {
249279 argsToExpand += 1
250280 }
251281
252- guard includedArg else {
282+ guard includedArg || ifIncluded == nil else {
253283 return nil
254284 }
255285
256286 var expandedArgs = [ LabeledExprSyntax] ( )
257287 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)
259289 guard edits. count == 1 , let edit = edits. first, !edit. replacement. isEmpty else {
260290 return nil
261291 }
@@ -291,7 +321,7 @@ fileprivate enum EditorPlaceholderData {
291321 case typed( text: Substring , type: TypeSyntax )
292322
293323 init ? ( token: TokenSyntax ) {
294- guard ExpandEditorPlaceholder . isPlaceholder ( token. text) else {
324+ guard isPlaceholder ( token. text) else {
295325 return nil
296326 }
297327
@@ -309,8 +339,8 @@ fileprivate enum EditorPlaceholderData {
309339 var typeText : Substring
310340 ( text, typeText) = split ( text, separatedBy: " ## " )
311341 if typeText. isEmpty {
312- // No type information
313- self = . basic ( text : text)
342+ // Only type information present
343+ self . init ( typeText : text)
314344 return
315345 }
316346
@@ -346,6 +376,21 @@ fileprivate enum EditorPlaceholderData {
346376 }
347377}
348378
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+
349394/// Split the given string into two components on the first instance of
350395/// `separatedBy`. The second element is empty if `separatedBy` is missing
351396/// from the initial string.
0 commit comments