Skip to content

Commit e286081

Browse files
committed
Merge pull request #230 from allevato/multiple-trailing-closures
Format multiple trailing closures.
1 parent 24084c6 commit e286081

File tree

2 files changed

+80
-6
lines changed

2 files changed

+80
-6
lines changed

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

+42-6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
5454
/// moved past these tokens.
5555
private var closingDelimiterTokens = Set<TokenSyntax>()
5656

57+
/// Tracks closures that are never allowed to be laid out entirely on one line (e.g., closures
58+
/// in a function call containing multiple trailing closures).
59+
private var forcedBreakingClosures = Set<SyntaxIdentifier>()
60+
5761
init(configuration: Configuration, operatorContext: OperatorContext) {
5862
self.config = configuration
5963
self.operatorContext = operatorContext
@@ -870,6 +874,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
870874
override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
871875
preVisitInsertingContextualBreaks(node)
872876

877+
// If there are multiple trailing closures, force all the closures in the call to break.
878+
if let additionalTrailingClosures = node.additionalTrailingClosures {
879+
if let closure = node.trailingClosure {
880+
forcedBreakingClosures.insert(closure.id)
881+
}
882+
for additionalTrailingClosure in additionalTrailingClosures {
883+
forcedBreakingClosures.insert(additionalTrailingClosure.closure.id)
884+
}
885+
}
886+
873887
if let calledMemberAccessExpr = node.calledExpression.as(MemberAccessExprSyntax.self) {
874888
if let base = calledMemberAccessExpr.base, base.is(IdentifierExprSyntax.self) {
875889
// When this function call is wrapped by a try-expr, the group applied when visiting the
@@ -907,6 +921,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
907921
clearContextualBreakState(node)
908922
}
909923

924+
override func visit(_ node: MultipleTrailingClosureElementSyntax)
925+
-> SyntaxVisitorContinueKind
926+
{
927+
before(node.label, tokens: .space)
928+
after(node.colon, tokens: .space)
929+
return .visitChildren
930+
}
931+
910932
/// Arrange the given argument list (or equivalently, tuple expression list) as a list of function
911933
/// arguments.
912934
///
@@ -979,12 +1001,19 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
9791001
}
9801002

9811003
override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
1004+
let newlineBehavior: NewlineBehavior
1005+
if forcedBreakingClosures.remove(node.id) != nil {
1006+
newlineBehavior = .soft
1007+
} else {
1008+
newlineBehavior = .elective
1009+
}
1010+
9821011
if let signature = node.signature {
9831012
after(node.leftBrace, tokens: .break(.open))
9841013
if node.statements.count > 0 {
985-
after(signature.inTok, tokens: .break(.same))
1014+
after(signature.inTok, tokens: .break(.same, newlines: newlineBehavior))
9861015
} else {
987-
after(signature.inTok, tokens: .break(.same, size: 0))
1016+
after(signature.inTok, tokens: .break(.same, size: 0, newlines: newlineBehavior))
9881017
}
9891018
before(node.rightBrace, tokens: .break(.close))
9901019
} else {
@@ -994,7 +1023,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
9941023
// or part of some other expression (where we want that expression's same/continue behavior to
9951024
// apply).
9961025
arrangeBracesAndContents(
997-
of: node, contentsKeyPath: \.statements, shouldResetBeforeLeftBrace: false)
1026+
of: node,
1027+
contentsKeyPath: \.statements,
1028+
shouldResetBeforeLeftBrace: false,
1029+
openBraceNewlineBehavior: newlineBehavior)
9981030
}
9991031
return .visitChildren
10001032
}
@@ -2537,10 +2569,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
25372569
/// if you have already placed a `reset` elsewhere (for example, in a `guard` statement, the
25382570
/// `reset` is inserted before the `else` keyword to force both it and the brace down to the
25392571
/// next line).
2572+
/// - openBraceNewlineBehavior: The newline behavior to apply to the break following the open
2573+
/// brace; defaults to `.elective`.
25402574
private func arrangeBracesAndContents<Node: BracedSyntax, BodyContents: SyntaxCollection>(
25412575
of node: Node?,
25422576
contentsKeyPath: KeyPath<Node, BodyContents>?,
2543-
shouldResetBeforeLeftBrace: Bool = true
2577+
shouldResetBeforeLeftBrace: Bool = true,
2578+
openBraceNewlineBehavior: NewlineBehavior = .elective
25442579
) where BodyContents.Element: SyntaxProtocol {
25452580
guard let node = node, let contentsKeyPath = contentsKeyPath else { return }
25462581

@@ -2550,10 +2585,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
25502585
tokens: .break(.reset, size: 1, newlines: .elective(ignoresDiscretionary: true)))
25512586
}
25522587
if !areBracesCompletelyEmpty(node, contentsKeyPath: contentsKeyPath) {
2553-
after(node.leftBrace, tokens: .break(.open, size: 1), .open)
2588+
after(
2589+
node.leftBrace, tokens: .break(.open, size: 1, newlines: openBraceNewlineBehavior), .open)
25542590
before(node.rightBrace, tokens: .break(.close, size: 1), .close)
25552591
} else {
2556-
after(node.leftBrace, tokens: .break(.open, size: 0))
2592+
after(node.leftBrace, tokens: .break(.open, size: 0, newlines: openBraceNewlineBehavior))
25572593
before(node.rightBrace, tokens: .break(.close, size: 0))
25582594
}
25592595
}

Tests/SwiftFormatPrettyPrintTests/FunctionCallTests.swift

+38
Original file line numberDiff line numberDiff line change
@@ -342,4 +342,42 @@ final class FunctionCallTests: PrettyPrintTestCase {
342342

343343
assertPrettyPrintEqual(input: input, expected: expected, linelength: 70)
344344
}
345+
346+
func testMultipleTrailingClosures() {
347+
let input =
348+
"""
349+
a = f { b } c: { d }
350+
let a = f { b } c: { d }
351+
let a = foo { b in b } c: { d in d }
352+
let a = foo { abcdefg in b } c: { d in d }
353+
"""
354+
355+
let expected =
356+
"""
357+
a = f {
358+
b
359+
} c: {
360+
d
361+
}
362+
let a = f {
363+
b
364+
} c: {
365+
d
366+
}
367+
let a = foo { b in
368+
b
369+
} c: { d in
370+
d
371+
}
372+
let a = foo {
373+
abcdefg in
374+
b
375+
} c: { d in
376+
d
377+
}
378+
379+
"""
380+
381+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 23)
382+
}
345383
}

0 commit comments

Comments
 (0)