Skip to content

Commit f9bd95a

Browse files
authored
Merge pull request swiftlang#132 from dylansturg/multiline_closure_args
Wrap closure params in open/close breaks to indent inside parens.
2 parents 0927db3 + 4e10b2b commit f9bd95a

File tree

2 files changed

+126
-9
lines changed

2 files changed

+126
-9
lines changed

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -889,22 +889,43 @@ private final class TokenStreamCreator: SyntaxVisitor {
889889
}
890890

891891
override func visit(_ node: ClosureSignatureSyntax) -> SyntaxVisitorContinueKind {
892-
let consistency: GroupBreakStyle
893-
if let input = node.input, input.is(ClosureParamListSyntax.self) {
894-
consistency = argumentListConsistency()
895-
} else {
896-
consistency = .inconsistent
897-
}
898-
899892
before(node.firstToken, tokens: .open)
900893

901894
if let input = node.input {
902895
// We unconditionally put a break before the `in` keyword below, so we should only put a break
903896
// after the capture list's right bracket if there are arguments following it or we'll end up
904897
// with an extra space if the line doesn't wrap.
905898
after(node.capture?.rightSquare, tokens: .break(.same))
906-
before(input.firstToken, tokens: .open(consistency))
907-
after(input.lastToken, tokens: .close)
899+
900+
// When it's parenthesized, the input is a `ParameterClauseSyntax`. Otherwise, it's a
901+
// `ClosureParamListSyntax`. The parenthesized version is wrapped in open/close breaks so that
902+
// the parens create an extra level of indentation.
903+
if let parameterClause = input.as(ParameterClauseSyntax.self) {
904+
// Whether we should prioritize keeping ") throws -> <return_type>" together. We can only do
905+
// this if the closure has arguments.
906+
let keepOutputTogether =
907+
!parameterClause.parameterList.isEmpty && config.prioritizeKeepingFunctionOutputTogether
908+
909+
// Keep the output together by grouping from the right paren to the end of the output.
910+
if keepOutputTogether {
911+
// Due to visitation order, the matching .open break is added in ParameterClauseSyntax.
912+
// Since the output clause is optional but the in-token is required, placing the .close
913+
// before `inTok` ensures the close gets into the token stream.
914+
before(node.inTok, tokens: .close)
915+
} else {
916+
// Group outside of the parens, so that the argument list together, preferring to break
917+
// between the argument list and the output.
918+
before(input.firstToken, tokens: .open)
919+
after(input.lastToken, tokens: .close)
920+
}
921+
922+
arrangeParameterClause(parameterClause, forcesBreakBeforeRightParen: true)
923+
} else {
924+
// Group around the arguments, but don't use open/close breaks because there are no parens
925+
// to create a new scope.
926+
before(input.firstToken, tokens: .open(argumentListConsistency()))
927+
after(input.lastToken, tokens: .close)
928+
}
908929
}
909930

910931
before(node.throwsTok, tokens: .break)

Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class ClosureExprTests: PrettyPrintTestCase {
1414
funcCall(closure: { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10 in return s1 })
1515
funcCall(param1: 123, closure: { s1, s2, s3 in return s1 })
1616
funcCall(closure: { (s1: String, s2: String) -> Bool in return s1 > s2 })
17+
funcCall(closure: { (s1: String, s2: String, s3: String, s4: String, s5: String) -> Bool in return s1 > s2 })
1718
"""
1819

1920
let expected =
@@ -53,6 +54,15 @@ public class ClosureExprTests: PrettyPrintTestCase {
5354
(s1: String, s2: String) -> Bool in
5455
return s1 > s2
5556
})
57+
funcCall(closure: {
58+
(
59+
s1: String,
60+
s2: String,
61+
s3: String,
62+
s4: String,
63+
s5: String
64+
) -> Bool in return s1 > s2
65+
})
5666
5767
"""
5868

@@ -73,6 +83,7 @@ public class ClosureExprTests: PrettyPrintTestCase {
7383
funcCall(closure: { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10 in return s1 })
7484
funcCall(param1: 123, closure: { s1, s2, s3 in return s1 })
7585
funcCall(closure: { (s1: String, s2: String) -> Bool in return s1 > s2 })
86+
funcCall(closure: { (s1: String, s2: String, s3: String, s4: String, s5: String) -> Bool in return s1 > s2 })
7687
"""
7788

7889
let expected =
@@ -98,6 +109,12 @@ public class ClosureExprTests: PrettyPrintTestCase {
98109
(s1: String, s2: String) -> Bool in
99110
return s1 > s2
100111
})
112+
funcCall(closure: {
113+
(
114+
s1: String, s2: String, s3: String,
115+
s4: String, s5: String
116+
) -> Bool in return s1 > s2
117+
})
101118
102119
"""
103120

@@ -113,6 +130,7 @@ public class ClosureExprTests: PrettyPrintTestCase {
113130
funcCall(param1: 2) { $1 < $2 }
114131
funcCall(param1: 2) { s1, s2, s3 in return s1}
115132
funcCall(param1: 2) { s1, s2, s3, s4, s5 in return s1}
133+
funcCall(param1: 2) { (s1: String, s2: String, s3: String, s4: String, s5: String) -> Bool in return s1 > s2 }
116134
"""
117135

118136
let expected =
@@ -125,6 +143,12 @@ public class ClosureExprTests: PrettyPrintTestCase {
125143
funcCall(param1: 2) {
126144
s1, s2, s3, s4, s5 in return s1
127145
}
146+
funcCall(param1: 2) {
147+
(
148+
s1: String, s2: String, s3: String,
149+
s4: String, s5: String
150+
) -> Bool in return s1 > s2
151+
}
128152
129153
"""
130154

@@ -345,4 +369,76 @@ public class ClosureExprTests: PrettyPrintTestCase {
345369

346370
assertPrettyPrintEqual(input: input, expected: expected, linelength: 60)
347371
}
372+
373+
public func testClosureOutputGrouping() {
374+
let input =
375+
"""
376+
funcCall(closure: {
377+
(s1: String, s2: String, s3: String) throws -> Bool in return s1 > s2
378+
})
379+
funcCall(closure: {
380+
(s1: String, s2: String, s3: String) throws -> AVeryLongReturnTypeThatOverflowsFiftyColumns in return s1 > s2
381+
})
382+
funcCall(closure: {
383+
() throws -> Bool in return s1 > s2
384+
})
385+
funcCall(closure: {
386+
() throws -> AVeryLongReturnTypeThatOverflowsFiftyColumns in return s1 > s2
387+
})
388+
"""
389+
390+
let expectedNotGroupingOutput =
391+
"""
392+
funcCall(closure: {
393+
(s1: String, s2: String, s3: String) throws
394+
-> Bool in return s1 > s2
395+
})
396+
funcCall(closure: {
397+
(s1: String, s2: String, s3: String) throws
398+
-> AVeryLongReturnTypeThatOverflowsFiftyColumns
399+
in return s1 > s2
400+
})
401+
funcCall(closure: {
402+
() throws -> Bool in return s1 > s2
403+
})
404+
funcCall(closure: {
405+
() throws
406+
-> AVeryLongReturnTypeThatOverflowsFiftyColumns
407+
in return s1 > s2
408+
})
409+
410+
"""
411+
412+
assertPrettyPrintEqual(input: input, expected: expectedNotGroupingOutput, linelength: 50)
413+
414+
let expectedKeepingOutputTogether =
415+
"""
416+
funcCall(closure: {
417+
(
418+
s1: String, s2: String, s3: String
419+
) throws -> Bool in return s1 > s2
420+
})
421+
funcCall(closure: {
422+
(
423+
s1: String, s2: String, s3: String
424+
) throws
425+
-> AVeryLongReturnTypeThatOverflowsFiftyColumns
426+
in return s1 > s2
427+
})
428+
funcCall(closure: {
429+
() throws -> Bool in return s1 > s2
430+
})
431+
funcCall(closure: {
432+
() throws
433+
-> AVeryLongReturnTypeThatOverflowsFiftyColumns
434+
in return s1 > s2
435+
})
436+
437+
"""
438+
439+
var config = Configuration()
440+
config.prioritizeKeepingFunctionOutputTogether = true
441+
assertPrettyPrintEqual(
442+
input: input, expected: expectedKeepingOutputTogether, linelength: 50, configuration: config)
443+
}
348444
}

0 commit comments

Comments
 (0)