Skip to content

Commit 6a8d51a

Browse files
committed
Merge pull request #553 from allevato/if-switch-exprs
Improve wrapping of if/switch expressions.
1 parent 90c1390 commit 6a8d51a

File tree

4 files changed

+195
-48
lines changed

4 files changed

+195
-48
lines changed

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 96 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1948,13 +1948,18 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
19481948

19491949
// If the rhs starts with a parenthesized expression, stack indentation around it.
19501950
// Otherwise, use regular continuation breaks.
1951-
if let (unindentingNode, _, breakKind, _) =
1951+
if let (unindentingNode, _, breakKind, shouldGroup) =
19521952
stackedIndentationBehavior(after: binOp, rhs: rhs)
19531953
{
19541954
beforeTokens = [.break(.open(kind: breakKind))]
1955+
var afterTokens: [Token] = [.break(.close(mustBreak: false), size: 0)]
1956+
if shouldGroup {
1957+
beforeTokens.append(.open)
1958+
afterTokens.append(.close)
1959+
}
19551960
after(
19561961
unindentingNode.lastToken(viewMode: .sourceAccurate),
1957-
tokens: [.break(.close(mustBreak: false), size: 0)])
1962+
tokens: afterTokens)
19581963
} else {
19591964
beforeTokens = [.break(.continue)]
19601965
}
@@ -2104,9 +2109,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
21042109
if let initializer = node.initializer {
21052110
let expr = initializer.value
21062111

2107-
if let (unindentingNode, _, breakKind, _) = stackedIndentationBehavior(rhs: expr) {
2108-
after(initializer.equal, tokens: .break(.open(kind: breakKind)))
2109-
after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0))
2112+
if let (unindentingNode, _, breakKind, shouldGroup) = stackedIndentationBehavior(rhs: expr) {
2113+
var openTokens: [Token] = [.break(.open(kind: breakKind))]
2114+
if shouldGroup {
2115+
openTokens.append(.open)
2116+
}
2117+
after(initializer.equal, tokens: openTokens)
2118+
var closeTokens: [Token] = [.break(.close(mustBreak: false), size: 0)]
2119+
if shouldGroup {
2120+
closeTokens.append(.close)
2121+
}
2122+
after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: closeTokens)
21102123
} else {
21112124
after(initializer.equal, tokens: .break(.continue))
21122125
}
@@ -2273,9 +2286,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
22732286
override func visit(_ node: InitializerClauseSyntax) -> SyntaxVisitorContinueKind {
22742287
before(node.equal, tokens: .space)
22752288

2276-
// InitializerClauses that are children of a PatternBindingSyntax are already handled in the
2277-
// latter node, to ensure that continuations stack appropriately.
2278-
if node.parent == nil || !node.parent!.is(PatternBindingSyntax.self) {
2289+
// InitializerClauses that are children of a PatternBindingSyntax or
2290+
// OptionalBindingConditionSyntax are already handled in the latter node, to ensure that
2291+
// continuations stack appropriately.
2292+
if let parent = node.parent,
2293+
!parent.is(PatternBindingSyntax.self)
2294+
&& !parent.is(OptionalBindingConditionSyntax.self)
2295+
{
22792296
after(node.equal, tokens: .break)
22802297
}
22812298
return .visitChildren
@@ -2457,6 +2474,26 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
24572474
after(typeAnnotation.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0))
24582475
}
24592476

2477+
if let initializer = node.initializer {
2478+
if let (unindentingNode, _, breakKind, shouldGroup) =
2479+
stackedIndentationBehavior(rhs: initializer.value)
2480+
{
2481+
var openTokens: [Token] = [.break(.open(kind: breakKind))]
2482+
if shouldGroup {
2483+
openTokens.append(.open)
2484+
}
2485+
after(initializer.equal, tokens: openTokens)
2486+
2487+
var closeTokens: [Token] = [.break(.close(mustBreak: false), size: 0)]
2488+
if shouldGroup {
2489+
closeTokens.append(.close)
2490+
}
2491+
after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: closeTokens)
2492+
} else {
2493+
after(initializer.equal, tokens: .break(.continue))
2494+
}
2495+
}
2496+
24602497
return .visitChildren
24612498
}
24622499

@@ -3372,47 +3409,63 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
33723409
}
33733410
}
33743411

3375-
/// Walks the expression and returns the leftmost multiline string literal (which might be the
3376-
/// expression itself) if the leftmost child is a multiline string literal or if it is a unary
3377-
/// operation applied to a multiline string literal.
3412+
/// Walks the expression and returns the leftmost subexpression (which might be the expression
3413+
/// itself) if the leftmost child is a node of the given type or if it is a unary operation
3414+
/// applied to a node of the given type.
33783415
///
3379-
/// - Parameter expr: The expression whose leftmost multiline string literal should be returned.
3380-
/// - Returns: The leftmost multiline string literal, or nil if the leftmost subexpression was
3381-
/// not a multiline string literal.
3382-
private func leftmostMultilineStringLiteral(of expr: ExprSyntax) -> StringLiteralExprSyntax? {
3416+
/// - Parameter expr: The expression whose leftmost matching subexpression should be returned.
3417+
/// - Returns: The leftmost subexpression, or nil if the leftmost subexpression was not the
3418+
/// desired type.
3419+
private func leftmostExpr(
3420+
of expr: ExprSyntax,
3421+
ifMatching predicate: (ExprSyntax) -> Bool
3422+
) -> ExprSyntax? {
3423+
if predicate(expr) {
3424+
return expr
3425+
}
33833426
switch Syntax(expr).as(SyntaxEnum.self) {
3384-
case .stringLiteralExpr(let stringLiteralExpr)
3385-
where stringLiteralExpr.openQuote.tokenKind == .multilineStringQuote:
3386-
return stringLiteralExpr
33873427
case .infixOperatorExpr(let infixOperatorExpr):
3388-
return leftmostMultilineStringLiteral(of: infixOperatorExpr.leftOperand)
3428+
return leftmostExpr(of: infixOperatorExpr.leftOperand, ifMatching: predicate)
33893429
case .asExpr(let asExpr):
3390-
return leftmostMultilineStringLiteral(of: asExpr.expression)
3430+
return leftmostExpr(of: asExpr.expression, ifMatching: predicate)
33913431
case .isExpr(let isExpr):
3392-
return leftmostMultilineStringLiteral(of: isExpr.expression)
3432+
return leftmostExpr(of: isExpr.expression, ifMatching: predicate)
33933433
case .forcedValueExpr(let forcedValueExpr):
3394-
return leftmostMultilineStringLiteral(of: forcedValueExpr.expression)
3434+
return leftmostExpr(of: forcedValueExpr.expression, ifMatching: predicate)
33953435
case .optionalChainingExpr(let optionalChainingExpr):
3396-
return leftmostMultilineStringLiteral(of: optionalChainingExpr.expression)
3436+
return leftmostExpr(of: optionalChainingExpr.expression, ifMatching: predicate)
33973437
case .postfixUnaryExpr(let postfixUnaryExpr):
3398-
return leftmostMultilineStringLiteral(of: postfixUnaryExpr.expression)
3438+
return leftmostExpr(of: postfixUnaryExpr.expression, ifMatching: predicate)
33993439
case .prefixOperatorExpr(let prefixOperatorExpr):
3400-
return leftmostMultilineStringLiteral(of: prefixOperatorExpr.postfixExpression)
3440+
return leftmostExpr(of: prefixOperatorExpr.postfixExpression, ifMatching: predicate)
34013441
case .ternaryExpr(let ternaryExpr):
3402-
return leftmostMultilineStringLiteral(of: ternaryExpr.conditionExpression)
3442+
return leftmostExpr(of: ternaryExpr.conditionExpression, ifMatching: predicate)
34033443
case .functionCallExpr(let functionCallExpr):
3404-
return leftmostMultilineStringLiteral(of: functionCallExpr.calledExpression)
3444+
return leftmostExpr(of: functionCallExpr.calledExpression, ifMatching: predicate)
34053445
case .subscriptExpr(let subscriptExpr):
3406-
return leftmostMultilineStringLiteral(of: subscriptExpr.calledExpression)
3446+
return leftmostExpr(of: subscriptExpr.calledExpression, ifMatching: predicate)
34073447
case .memberAccessExpr(let memberAccessExpr):
3408-
return memberAccessExpr.base.flatMap { leftmostMultilineStringLiteral(of: $0) }
3448+
return memberAccessExpr.base.flatMap { leftmostExpr(of: $0, ifMatching: predicate) }
34093449
case .postfixIfConfigExpr(let postfixIfConfigExpr):
3410-
return postfixIfConfigExpr.base.flatMap { leftmostMultilineStringLiteral(of: $0) }
3450+
return postfixIfConfigExpr.base.flatMap { leftmostExpr(of: $0, ifMatching: predicate) }
34113451
default:
34123452
return nil
34133453
}
34143454
}
34153455

3456+
/// Walks the expression and returns the leftmost multiline string literal (which might be the
3457+
/// expression itself) if the leftmost child is a multiline string literal or if it is a unary
3458+
/// operation applied to a multiline string literal.
3459+
///
3460+
/// - Parameter expr: The expression whose leftmost multiline string literal should be returned.
3461+
/// - Returns: The leftmost multiline string literal, or nil if the leftmost subexpression was
3462+
/// not a multiline string literal.
3463+
private func leftmostMultilineStringLiteral(of expr: ExprSyntax) -> StringLiteralExprSyntax? {
3464+
return leftmostExpr(of: expr) {
3465+
$0.as(StringLiteralExprSyntax.self)?.openQuote.tokenKind == .multilineStringQuote
3466+
}?.as(StringLiteralExprSyntax.self)
3467+
}
3468+
34163469
/// Returns the outermost node enclosing the given node whose closing delimiter(s) must be kept
34173470
/// alongside the last token of the given node. Any tokens between `node.lastToken` and the
34183471
/// returned node's `lastToken` are delimiter tokens that shouldn't be preceded by a break.
@@ -3503,7 +3556,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
35033556
unindentingNode: unindentingParenExpr,
35043557
shouldReset: true,
35053558
breakKind: .continuation,
3506-
shouldGroup: true
3559+
shouldGroup: false
35073560
)
35083561
}
35093562

@@ -3519,7 +3572,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
35193572
unindentingNode: Syntax(parenthesizedExpr),
35203573
shouldReset: false,
35213574
breakKind: .continuation,
3522-
shouldGroup: true
3575+
shouldGroup: false
35233576
)
35243577
}
35253578

@@ -3535,6 +3588,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
35353588
)
35363589
}
35373590

3591+
if let leftmostExpr = leftmostExpr(of: rhs, ifMatching: {
3592+
$0.is(IfExprSyntax.self) || $0.is(SwitchExprSyntax.self)
3593+
}) {
3594+
return (
3595+
unindentingNode: Syntax(leftmostExpr),
3596+
shouldReset: false,
3597+
breakKind: .block,
3598+
shouldGroup: true
3599+
)
3600+
}
3601+
35383602
// Otherwise, don't stack--use regular continuation breaks instead.
35393603
return nil
35403604
}

Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,14 @@ final class IfStmtTests: PrettyPrintTestCase {
206206
let expected =
207207
"""
208208
func foo() -> Int {
209-
let x = if var1 < var2 {
210-
23
211-
} else if d < e {
212-
24
213-
} else {
214-
0
215-
}
209+
let x =
210+
if var1 < var2 {
211+
23
212+
} else if d < e {
213+
24
214+
} else {
215+
0
216+
}
216217
return x
217218
}
218219
@@ -221,6 +222,39 @@ final class IfStmtTests: PrettyPrintTestCase {
221222
assertPrettyPrintEqual(input: input, expected: expected, linelength: 26)
222223
}
223224

225+
func testIfExpression3() {
226+
let input =
227+
"""
228+
let x = if a { b } else { c }
229+
xyzab = if a { b } else { c }
230+
"""
231+
assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 80)
232+
233+
let expected28 =
234+
"""
235+
let x =
236+
if a { b } else { c }
237+
xyzab =
238+
if a { b } else { c }
239+
240+
"""
241+
assertPrettyPrintEqual(input: input, expected: expected28, linelength: 28)
242+
243+
let expected22 =
244+
"""
245+
let x =
246+
if a { b } else {
247+
c
248+
}
249+
xyzab =
250+
if a { b } else {
251+
c
252+
}
253+
254+
"""
255+
assertPrettyPrintEqual(input: input, expected: expected22, linelength: 22)
256+
}
257+
224258
func testMatchingPatternConditions() {
225259
let input =
226260
"""

Tests/SwiftFormatPrettyPrintTests/StringTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,33 @@ final class StringTests: PrettyPrintTestCase {
419419
assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 100)
420420
}
421421

422+
func testMultilineStringsNestedInAnotherWrappingContext() {
423+
let input =
424+
#"""
425+
guard
426+
let x = """
427+
blah
428+
blah
429+
""".data(using: .utf8) {
430+
print(x)
431+
}
432+
"""#
433+
434+
let expected =
435+
#"""
436+
guard
437+
let x = """
438+
blah
439+
blah
440+
""".data(using: .utf8)
441+
{
442+
print(x)
443+
}
444+
445+
"""#
446+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 100)
447+
}
448+
422449
func testEmptyMultilineStrings() {
423450
let input =
424451
##"""

Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -297,20 +297,42 @@ final class SwitchStmtTests: PrettyPrintTestCase {
297297
let expected =
298298
"""
299299
func foo() -> Int {
300-
let x = switch value1 + value2 + value3 + value4 {
301-
case "a":
302-
0
303-
case "b":
304-
1
305-
default:
306-
2
307-
}
300+
let x =
301+
switch value1 + value2 + value3 + value4 {
302+
case "a":
303+
0
304+
case "b":
305+
1
306+
default:
307+
2
308+
}
309+
return x
310+
}
311+
312+
"""
313+
314+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 46)
315+
316+
let expected43 =
317+
"""
318+
func foo() -> Int {
319+
let x =
320+
switch value1 + value2 + value3
321+
+ value4
322+
{
323+
case "a":
324+
0
325+
case "b":
326+
1
327+
default:
328+
2
329+
}
308330
return x
309331
}
310332
311333
"""
312334

313-
assertPrettyPrintEqual(input: input, expected: expected, linelength: 52)
335+
assertPrettyPrintEqual(input: input, expected: expected43, linelength: 43)
314336
}
315337

316338
func testUnknownDefault() {

0 commit comments

Comments
 (0)