@@ -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,29 @@ 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 `allowNestedPlaceholders = false`
90+ /// ```swift
91+ /// { someInt in <#String#> }
92+ /// ```
93+ ///
94+ /// With `allowNestedPlaceholders = true`
95+ /// ```swift
96+ /// <#{ someInt in <#String#> }#>
97+ /// ```
98+ let allowNestedPlaceholders : Bool
99+
100+ init (
101+ closureLiteralFormat: BasicFormat = BasicFormat ( ) ,
102+ allowNestedPlaceholders: Bool = false
103+ ) {
104+ self . closureLiteralFormat = closureLiteralFormat
105+ self . allowNestedPlaceholders = allowNestedPlaceholders
87106 }
88107 }
89108
@@ -94,16 +113,17 @@ struct ExpandSingleEditorPlaceholder: EditRefactoringProvider {
94113
95114 let expanded : String
96115 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
116+ let format = context. closureLiteralFormat
117+ let initialIndentation = format. currentIndentationLevel
118+ var formattedExpansion = functionType. closureExpansion. formatted ( using: format) . description
102119 // Strip the initial indentation from the placeholder itself. We only introduced the initial indentation to
103120 // format consecutive lines. We don't want it at the front of the initial line because it replaces an expression
104121 // 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) )
122+ if formattedExpansion. hasPrefix ( initialIndentation. description) {
123+ formattedExpansion = String ( formattedExpansion. dropFirst ( initialIndentation. description. count) )
124+ }
125+ if context. allowNestedPlaceholders {
126+ formattedExpansion = wrapInPlaceholder ( formattedExpansion)
107127 }
108128 expanded = formattedExpansion
109129 } else {
@@ -161,20 +181,24 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
161181 let arg = placeholder. parent? . as ( LabeledExprSyntax . self) ,
162182 let argList = arg. parent? . as ( LabeledExprListSyntax . self) ,
163183 let call = argList. parent? . as ( FunctionCallExprSyntax . self) ,
164- let expandedTrailingClosures = ExpandEditorPlaceholdersToTrailingClosures . expandTrailingClosurePlaceholders (
184+ let expandedClosures = ExpandEditorPlaceholdersToLiteralClosures . expandClosurePlaceholders (
165185 in: call,
166186 ifIncluded: arg,
167- indentationWidth: context. indentationWidth
187+ context: ExpandEditorPlaceholdersToLiteralClosures . Context (
188+ format: . trailing( indentationWidth: context. indentationWidth)
189+ )
168190 )
169191 else {
170192 return ExpandSingleEditorPlaceholder . textRefactor ( syntax: token)
171193 }
172194
173- return [ SourceEdit . replace ( call, with: expandedTrailingClosures . description) ]
195+ return [ SourceEdit . replace ( call, with: expandedClosures . description) ]
174196 }
175197}
176198
177- /// Expand all the editor placeholders in the function call that can be converted to trailing closures.
199+ /// Expand all the editor placeholders in the function call to literal closures.
200+ /// By default they will be expanded to trailing form; if you provide your own
201+ /// formatter via ``Context/format`` they will be expanded inline.
178202///
179203/// ## Before
180204/// ```swift
@@ -185,7 +209,7 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
185209/// )
186210/// ```
187211///
188- /// ## Expansion of `foo`
212+ /// ## Expansion of `foo`, default behavior
189213/// ```swift
190214/// foo(
191215/// arg: <#T##Int#>,
@@ -195,45 +219,98 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
195219/// <#T##String#>
196220/// }
197221/// ```
198- public struct ExpandEditorPlaceholdersToTrailingClosures : SyntaxRefactoringProvider {
222+ ///
223+ /// ## Expansion of `foo` with a basic custom formatter
224+ /// ```swift
225+ /// foo(
226+ /// arg: <#T##Int#>,
227+ /// firstClosure: { someInt in
228+ /// <#T##String#>
229+ /// },
230+ /// secondClosure: { someInt in
231+ /// <#T##String#>
232+ /// }
233+ /// )
234+ /// ```
235+ ///
236+ /// ## Expansion of `foo`, custom formatter with `allowNestedPlaceholders: true`
237+ /// ```swift
238+ /// foo(
239+ /// arg: <#T##Int#>,
240+ /// firstClosure: <#{ someInt in
241+ /// <#T##String#>
242+ /// }#>,
243+ /// secondClosure: <#{ someInt in
244+ /// <#T##String#>
245+ /// }#>
246+ /// )
247+ /// ```
248+ public struct ExpandEditorPlaceholdersToLiteralClosures : SyntaxRefactoringProvider {
199249 public struct Context {
200- public let indentationWidth : Trivia ?
250+ public enum Format {
251+ /// Default formatting behavior: expand to trailing closures.
252+ case trailing( indentationWidth: Trivia ? )
253+ /// Use the given formatter and expand the placeholder inline, without
254+ /// moving it to trailing position. If `allowNestedPlaceholders` is true,
255+ /// the entire closure will also be wrapped as a placeholder.
256+ case custom( BasicFormat , allowNestedPlaceholders: Bool )
257+ }
258+ public let format : Format
259+
260+ public init ( format: Format ) {
261+ self . format = format
262+ }
201263
202264 public init ( indentationWidth: Trivia ? = nil ) {
203- self . indentationWidth = indentationWidth
265+ self . init ( format : . trailing ( indentationWidth: indentationWidth) )
204266 }
205267 }
206268
207269 public static func refactor(
208270 syntax call: FunctionCallExprSyntax ,
209271 in context: Context = Context ( )
210272 ) -> FunctionCallExprSyntax ? {
211- return Self . expandTrailingClosurePlaceholders ( in: call, ifIncluded: nil , indentationWidth: context. indentationWidth)
273+ return Self . expandClosurePlaceholders (
274+ in: call,
275+ ifIncluded: nil ,
276+ context: context
277+ )
212278 }
213279
214280 /// If the given argument is `nil` or one of the last arguments that are all
215281 /// function-typed placeholders and this call doesn't have a trailing
216282 /// closure, then return a replacement of this call with one that uses
217283 /// closures based on the function types provided by each editor placeholder.
218284 /// Otherwise return nil.
219- fileprivate static func expandTrailingClosurePlaceholders (
285+ fileprivate static func expandClosurePlaceholders (
220286 in call: FunctionCallExprSyntax ,
221287 ifIncluded arg: LabeledExprSyntax ? ,
222- indentationWidth : Trivia ?
288+ context : Context
223289 ) -> FunctionCallExprSyntax ? {
224- guard let expanded = call. expandTrailingClosurePlaceholders ( ifIncluded: arg, indentationWidth: indentationWidth)
225- else {
226- return nil
227- }
290+ switch context. format {
291+ case let . custom( formatter, allowNestedPlaceholders: allowNesting) :
292+ let expanded = call. expandClosurePlaceholders (
293+ ifIncluded: arg,
294+ customFormat: formatter,
295+ allowNestedPlaceholders: allowNesting
296+ )
297+ return expanded? . expr
228298
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- }
299+ case let . trailing( indentationWidth) :
300+ guard let expanded = call. expandClosurePlaceholders ( ifIncluded: arg, indentationWidth: indentationWidth)
301+ else {
302+ return nil
303+ }
235304
236- return trailing
305+ let callToTrailingContext = CallToTrailingClosures . Context (
306+ startAtArgument: call. arguments. count - expanded. numClosures
307+ )
308+ guard let trailing = CallToTrailingClosures . refactor ( syntax: expanded. expr, in: callToTrailingContext) else {
309+ return nil
310+ }
311+
312+ return trailing
313+ }
237314 }
238315}
239316
@@ -311,9 +388,11 @@ extension FunctionCallExprSyntax {
311388 /// closure, then return a replacement of this call with one that uses
312389 /// closures based on the function types provided by each editor placeholder.
313390 /// Otherwise return nil.
314- fileprivate func expandTrailingClosurePlaceholders (
391+ fileprivate func expandClosurePlaceholders (
315392 ifIncluded: LabeledExprSyntax ? ,
316- indentationWidth: Trivia ?
393+ indentationWidth: Trivia ? = nil ,
394+ customFormat: BasicFormat ? = nil ,
395+ allowNestedPlaceholders: Bool = false
317396 ) -> ( expr: FunctionCallExprSyntax , numClosures: Int ) ? {
318397 var includedArg = false
319398 var argsToExpand = 0
@@ -343,8 +422,12 @@ extension FunctionCallExprSyntax {
343422 let edits = ExpandSingleEditorPlaceholder . textRefactor (
344423 syntax: arg. expression. cast ( DeclReferenceExprSyntax . self) . baseName,
345424 in: ExpandSingleEditorPlaceholder . Context (
346- indentationWidth: indentationWidth,
347- initialIndentation: lineIndentation
425+ closureLiteralFormat: customFormat
426+ ?? BasicFormat (
427+ indentationWidth: indentationWidth,
428+ initialIndentation: lineIndentation
429+ ) ,
430+ allowNestedPlaceholders: allowNestedPlaceholders
348431 )
349432 )
350433 guard edits. count == 1 , let edit = edits. first, !edit. replacement. isEmpty else {
0 commit comments