@@ -40,7 +40,9 @@ import SwiftSyntaxBuilder
4040/// `type-for-expansion-string`), is parsed into a syntax node. If that node is
4141/// a `FunctionTypeSyntax` then the placeholder is expanded into a
4242/// `ClosureExprSyntax`. Otherwise it is expanded as is, which is also the case
43- /// for when only a display string is provided.
43+ /// for when only a display string is provided. You may customize the formatting
44+ /// of a closure expansion via ``Context/closureLiteralFormat``, for example to
45+ /// change whether it is split onto multiple lines.
4446///
4547/// ## Function Typed Placeholder
4648/// ### Before
@@ -78,12 +80,30 @@ import SwiftSyntaxBuilder
7880/// ```
7981struct ExpandSingleEditorPlaceholder : EditRefactoringProvider {
8082 struct Context {
81- let indentationWidth : Trivia ?
82- let initialIndentation : Trivia
83-
84- init ( indentationWidth: Trivia ? = nil , initialIndentation: Trivia = [ ] ) {
85- self . indentationWidth = indentationWidth
86- self . initialIndentation = initialIndentation
83+ /// The formatter to use when expanding a function-typed placeholder.
84+ let closureLiteralFormat : BasicFormat
85+ /// When true, the expansion will wrap a function-typed placeholder's entire
86+ /// expansion in placeholder delimiters, in addition to any placeholders
87+ /// inside the expanded closure literal.
88+ ///
89+ /// ## With nested placeholders allowed
90+ /// ### Before
91+ /// ```swift
92+ /// <#T##(Int) -> String##(Int) -> String##(_ someInt: Int) -> String#>
93+ /// ```
94+ ///
95+ /// ### After
96+ /// ```swift
97+ /// <#{ someInt in <#String#> }#>
98+ /// ```
99+ let allowNestedPlaceholders : Bool
100+
101+ init (
102+ closureLiteralFormat: BasicFormat = BasicFormat ( ) ,
103+ allowNestedPlaceholders: Bool = false
104+ ) {
105+ self . closureLiteralFormat = closureLiteralFormat
106+ self . allowNestedPlaceholders = allowNestedPlaceholders
87107 }
88108 }
89109
@@ -94,16 +114,17 @@ struct ExpandSingleEditorPlaceholder: EditRefactoringProvider {
94114
95115 let expanded : String
96116 if let functionType = placeholder. typeForExpansion? . as ( FunctionTypeSyntax . self) {
97- let basicFormat = BasicFormat (
98- indentationWidth: context. indentationWidth,
99- initialIndentation: context. initialIndentation
100- )
101- var formattedExpansion = functionType. closureExpansion. formatted ( using: basicFormat) . description
117+ let format = context. closureLiteralFormat
118+ let initialIndentation = format. currentIndentationLevel
119+ var formattedExpansion = functionType. closureExpansion. formatted ( using: format) . description
102120 // Strip the initial indentation from the placeholder itself. We only introduced the initial indentation to
103121 // format consecutive lines. We don't want it at the front of the initial line because it replaces an expression
104122 // that might be in the middle of a line.
105- if formattedExpansion. hasPrefix ( context. initialIndentation. description) {
106- formattedExpansion = String ( formattedExpansion. dropFirst ( context. initialIndentation. description. count) )
123+ if formattedExpansion. hasPrefix ( initialIndentation. description) {
124+ formattedExpansion = String ( formattedExpansion. dropFirst ( initialIndentation. description. count) )
125+ }
126+ if context. allowNestedPlaceholders {
127+ formattedExpansion = wrapInPlaceholder ( formattedExpansion)
107128 }
108129 expanded = formattedExpansion
109130 } else {
@@ -161,20 +182,24 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
161182 let arg = placeholder. parent? . as ( LabeledExprSyntax . self) ,
162183 let argList = arg. parent? . as ( LabeledExprListSyntax . self) ,
163184 let call = argList. parent? . as ( FunctionCallExprSyntax . self) ,
164- let expandedTrailingClosures = ExpandEditorPlaceholdersToTrailingClosures . expandTrailingClosurePlaceholders (
185+ let expandedClosures = ExpandEditorPlaceholdersToLiteralClosures . expandClosurePlaceholders (
165186 in: call,
166187 ifIncluded: arg,
167- indentationWidth: context. indentationWidth
188+ context: ExpandEditorPlaceholdersToLiteralClosures . Context (
189+ format: . trailing( indentationWidth: context. indentationWidth)
190+ )
168191 )
169192 else {
170193 return ExpandSingleEditorPlaceholder . textRefactor ( syntax: token)
171194 }
172195
173- return [ SourceEdit . replace ( call, with: expandedTrailingClosures . description) ]
196+ return [ SourceEdit . replace ( call, with: expandedClosures . description) ]
174197 }
175198}
176199
177- /// Expand all the editor placeholders in the function call that can be converted to trailing closures.
200+ /// Expand all the editor placeholders in the function call to literal closures.
201+ /// By default they will be expanded to trailing form; if you provide your own
202+ /// formatter via ``Context/format`` they will be expanded inline.
178203///
179204/// ## Before
180205/// ```swift
@@ -185,7 +210,7 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
185210/// )
186211/// ```
187212///
188- /// ## Expansion of `foo`
213+ /// ## Expansion of `foo`, default behavior
189214/// ```swift
190215/// foo(
191216/// arg: <#T##Int#>,
@@ -195,45 +220,98 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
195220/// <#T##String#>
196221/// }
197222/// ```
198- public struct ExpandEditorPlaceholdersToTrailingClosures : SyntaxRefactoringProvider {
223+ ///
224+ /// ## Expansion of `foo` with a basic custom formatter
225+ /// ```swift
226+ /// foo(
227+ /// arg: <#T##Int#>,
228+ /// firstClosure: { someInt in
229+ /// <#T##String#>
230+ /// },
231+ /// secondClosure: { someInt in
232+ /// <#T##String#>
233+ /// }
234+ /// )
235+ /// ```
236+ ///
237+ /// ## Expansion of `foo`, custom formatter with `allowNestedPlaceholders: true`
238+ /// ```swift
239+ /// foo(
240+ /// arg: <#T##Int#>,
241+ /// firstClosure: <#{ someInt in
242+ /// <#T##String#>
243+ /// }#>,
244+ /// secondClosure: <#{ someInt in
245+ /// <#T##String#>
246+ /// }#>
247+ /// )
248+ /// ```
249+ public struct ExpandEditorPlaceholdersToLiteralClosures : SyntaxRefactoringProvider {
199250 public struct Context {
200- public let indentationWidth : Trivia ?
251+ public enum Format {
252+ /// Default formatting behavior: expand to trailing closures.
253+ case trailing( indentationWidth: Trivia ? )
254+ /// Use the given formatter and expand the placeholder inline, without
255+ /// moving it to trailing position. If `allowNestedPlaceholders` is true,
256+ /// the entire closure will also be wrapped as a placeholder.
257+ case custom( BasicFormat , allowNestedPlaceholders: Bool )
258+ }
259+ public let format : Format
260+
261+ public init ( format: Format ) {
262+ self . format = format
263+ }
201264
202265 public init ( indentationWidth: Trivia ? = nil ) {
203- self . indentationWidth = indentationWidth
266+ self . init ( format : . trailing ( indentationWidth: indentationWidth) )
204267 }
205268 }
206269
207270 public static func refactor(
208271 syntax call: FunctionCallExprSyntax ,
209272 in context: Context = Context ( )
210273 ) -> FunctionCallExprSyntax ? {
211- return Self . expandTrailingClosurePlaceholders ( in: call, ifIncluded: nil , indentationWidth: context. indentationWidth)
274+ return Self . expandClosurePlaceholders (
275+ in: call,
276+ ifIncluded: nil ,
277+ context: context
278+ )
212279 }
213280
214281 /// If the given argument is `nil` or one of the last arguments that are all
215282 /// function-typed placeholders and this call doesn't have a trailing
216283 /// closure, then return a replacement of this call with one that uses
217284 /// closures based on the function types provided by each editor placeholder.
218285 /// Otherwise return nil.
219- fileprivate static func expandTrailingClosurePlaceholders (
286+ fileprivate static func expandClosurePlaceholders (
220287 in call: FunctionCallExprSyntax ,
221288 ifIncluded arg: LabeledExprSyntax ? ,
222- indentationWidth : Trivia ?
289+ context : Context
223290 ) -> FunctionCallExprSyntax ? {
224- guard let expanded = call. expandTrailingClosurePlaceholders ( ifIncluded: arg, indentationWidth: indentationWidth)
225- else {
226- return nil
227- }
291+ switch context. format {
292+ case let . custom( formatter, allowNestedPlaceholders: allowNesting) :
293+ let expanded = call. expandClosurePlaceholders (
294+ ifIncluded: arg,
295+ customFormat: formatter,
296+ allowNestedPlaceholders: allowNesting
297+ )
298+ return expanded? . expr
228299
229- let callToTrailingContext = CallToTrailingClosures . Context (
230- startAtArgument: call. arguments. count - expanded. numClosures
231- )
232- guard let trailing = CallToTrailingClosures . refactor ( syntax: expanded. expr, in: callToTrailingContext) else {
233- return nil
234- }
300+ case let . trailing( indentationWidth) :
301+ guard let expanded = call. expandClosurePlaceholders ( ifIncluded: arg, indentationWidth: indentationWidth)
302+ else {
303+ return nil
304+ }
235305
236- return trailing
306+ let callToTrailingContext = CallToTrailingClosures . Context (
307+ startAtArgument: call. arguments. count - expanded. numClosures
308+ )
309+ guard let trailing = CallToTrailingClosures . refactor ( syntax: expanded. expr, in: callToTrailingContext) else {
310+ return nil
311+ }
312+
313+ return trailing
314+ }
237315 }
238316}
239317
@@ -311,9 +389,11 @@ extension FunctionCallExprSyntax {
311389 /// closure, then return a replacement of this call with one that uses
312390 /// closures based on the function types provided by each editor placeholder.
313391 /// Otherwise return nil.
314- fileprivate func expandTrailingClosurePlaceholders (
392+ fileprivate func expandClosurePlaceholders (
315393 ifIncluded: LabeledExprSyntax ? ,
316- indentationWidth: Trivia ?
394+ indentationWidth: Trivia ? = nil ,
395+ customFormat: BasicFormat ? = nil ,
396+ allowNestedPlaceholders: Bool = false
317397 ) -> ( expr: FunctionCallExprSyntax , numClosures: Int ) ? {
318398 var includedArg = false
319399 var argsToExpand = 0
@@ -343,8 +423,12 @@ extension FunctionCallExprSyntax {
343423 let edits = ExpandSingleEditorPlaceholder . textRefactor (
344424 syntax: arg. expression. cast ( DeclReferenceExprSyntax . self) . baseName,
345425 in: ExpandSingleEditorPlaceholder . Context (
346- indentationWidth: indentationWidth,
347- initialIndentation: lineIndentation
426+ closureLiteralFormat: customFormat
427+ ?? BasicFormat (
428+ indentationWidth: indentationWidth,
429+ initialIndentation: lineIndentation
430+ ) ,
431+ allowNestedPlaceholders: allowNestedPlaceholders
348432 )
349433 )
350434 guard edits. count == 1 , let edit = edits. first, !edit. replacement. isEmpty else {
0 commit comments