Skip to content

Commit a41b528

Browse files
authored
Merge pull request #553 from allevato/if-switch-exprs
Improve wrapping of if/switch expressions.
2 parents 2480f73 + 0a9b7c7 commit a41b528

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
@@ -1965,13 +1965,18 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
19651965

19661966
// If the rhs starts with a parenthesized expression, stack indentation around it.
19671967
// Otherwise, use regular continuation breaks.
1968-
if let (unindentingNode, _, breakKind, _) =
1968+
if let (unindentingNode, _, breakKind, shouldGroup) =
19691969
stackedIndentationBehavior(after: binOp, rhs: rhs)
19701970
{
19711971
beforeTokens = [.break(.open(kind: breakKind))]
1972+
var afterTokens: [Token] = [.break(.close(mustBreak: false), size: 0)]
1973+
if shouldGroup {
1974+
beforeTokens.append(.open)
1975+
afterTokens.append(.close)
1976+
}
19721977
after(
19731978
unindentingNode.lastToken(viewMode: .sourceAccurate),
1974-
tokens: [.break(.close(mustBreak: false), size: 0)])
1979+
tokens: afterTokens)
19751980
} else {
19761981
beforeTokens = [.break(.continue)]
19771982
}
@@ -2121,9 +2126,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
21212126
if let initializer = node.initializer {
21222127
let expr = initializer.value
21232128

2124-
if let (unindentingNode, _, breakKind, _) = stackedIndentationBehavior(rhs: expr) {
2125-
after(initializer.equal, tokens: .break(.open(kind: breakKind)))
2126-
after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0))
2129+
if let (unindentingNode, _, breakKind, shouldGroup) = stackedIndentationBehavior(rhs: expr) {
2130+
var openTokens: [Token] = [.break(.open(kind: breakKind))]
2131+
if shouldGroup {
2132+
openTokens.append(.open)
2133+
}
2134+
after(initializer.equal, tokens: openTokens)
2135+
var closeTokens: [Token] = [.break(.close(mustBreak: false), size: 0)]
2136+
if shouldGroup {
2137+
closeTokens.append(.close)
2138+
}
2139+
after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: closeTokens)
21272140
} else {
21282141
after(initializer.equal, tokens: .break(.continue))
21292142
}
@@ -2290,9 +2303,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
22902303
override func visit(_ node: InitializerClauseSyntax) -> SyntaxVisitorContinueKind {
22912304
before(node.equal, tokens: .space)
22922305

2293-
// InitializerClauses that are children of a PatternBindingSyntax are already handled in the
2294-
// latter node, to ensure that continuations stack appropriately.
2295-
if node.parent == nil || !node.parent!.is(PatternBindingSyntax.self) {
2306+
// InitializerClauses that are children of a PatternBindingSyntax or
2307+
// OptionalBindingConditionSyntax are already handled in the latter node, to ensure that
2308+
// continuations stack appropriately.
2309+
if let parent = node.parent,
2310+
!parent.is(PatternBindingSyntax.self)
2311+
&& !parent.is(OptionalBindingConditionSyntax.self)
2312+
{
22962313
after(node.equal, tokens: .break)
22972314
}
22982315
return .visitChildren
@@ -2474,6 +2491,26 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
24742491
after(typeAnnotation.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0))
24752492
}
24762493

2494+
if let initializer = node.initializer {
2495+
if let (unindentingNode, _, breakKind, shouldGroup) =
2496+
stackedIndentationBehavior(rhs: initializer.value)
2497+
{
2498+
var openTokens: [Token] = [.break(.open(kind: breakKind))]
2499+
if shouldGroup {
2500+
openTokens.append(.open)
2501+
}
2502+
after(initializer.equal, tokens: openTokens)
2503+
2504+
var closeTokens: [Token] = [.break(.close(mustBreak: false), size: 0)]
2505+
if shouldGroup {
2506+
closeTokens.append(.close)
2507+
}
2508+
after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: closeTokens)
2509+
} else {
2510+
after(initializer.equal, tokens: .break(.continue))
2511+
}
2512+
}
2513+
24772514
return .visitChildren
24782515
}
24792516

@@ -3389,47 +3426,63 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
33893426
}
33903427
}
33913428

3392-
/// Walks the expression and returns the leftmost multiline string literal (which might be the
3393-
/// expression itself) if the leftmost child is a multiline string literal or if it is a unary
3394-
/// operation applied to a multiline string literal.
3429+
/// Walks the expression and returns the leftmost subexpression (which might be the expression
3430+
/// itself) if the leftmost child is a node of the given type or if it is a unary operation
3431+
/// applied to a node of the given type.
33953432
///
3396-
/// - Parameter expr: The expression whose leftmost multiline string literal should be returned.
3397-
/// - Returns: The leftmost multiline string literal, or nil if the leftmost subexpression was
3398-
/// not a multiline string literal.
3399-
private func leftmostMultilineStringLiteral(of expr: ExprSyntax) -> StringLiteralExprSyntax? {
3433+
/// - Parameter expr: The expression whose leftmost matching subexpression should be returned.
3434+
/// - Returns: The leftmost subexpression, or nil if the leftmost subexpression was not the
3435+
/// desired type.
3436+
private func leftmostExpr(
3437+
of expr: ExprSyntax,
3438+
ifMatching predicate: (ExprSyntax) -> Bool
3439+
) -> ExprSyntax? {
3440+
if predicate(expr) {
3441+
return expr
3442+
}
34003443
switch Syntax(expr).as(SyntaxEnum.self) {
3401-
case .stringLiteralExpr(let stringLiteralExpr)
3402-
where stringLiteralExpr.openQuote.tokenKind == .multilineStringQuote:
3403-
return stringLiteralExpr
34043444
case .infixOperatorExpr(let infixOperatorExpr):
3405-
return leftmostMultilineStringLiteral(of: infixOperatorExpr.leftOperand)
3445+
return leftmostExpr(of: infixOperatorExpr.leftOperand, ifMatching: predicate)
34063446
case .asExpr(let asExpr):
3407-
return leftmostMultilineStringLiteral(of: asExpr.expression)
3447+
return leftmostExpr(of: asExpr.expression, ifMatching: predicate)
34083448
case .isExpr(let isExpr):
3409-
return leftmostMultilineStringLiteral(of: isExpr.expression)
3449+
return leftmostExpr(of: isExpr.expression, ifMatching: predicate)
34103450
case .forcedValueExpr(let forcedValueExpr):
3411-
return leftmostMultilineStringLiteral(of: forcedValueExpr.expression)
3451+
return leftmostExpr(of: forcedValueExpr.expression, ifMatching: predicate)
34123452
case .optionalChainingExpr(let optionalChainingExpr):
3413-
return leftmostMultilineStringLiteral(of: optionalChainingExpr.expression)
3453+
return leftmostExpr(of: optionalChainingExpr.expression, ifMatching: predicate)
34143454
case .postfixUnaryExpr(let postfixUnaryExpr):
3415-
return leftmostMultilineStringLiteral(of: postfixUnaryExpr.expression)
3455+
return leftmostExpr(of: postfixUnaryExpr.expression, ifMatching: predicate)
34163456
case .prefixOperatorExpr(let prefixOperatorExpr):
3417-
return leftmostMultilineStringLiteral(of: prefixOperatorExpr.postfixExpression)
3457+
return leftmostExpr(of: prefixOperatorExpr.postfixExpression, ifMatching: predicate)
34183458
case .ternaryExpr(let ternaryExpr):
3419-
return leftmostMultilineStringLiteral(of: ternaryExpr.conditionExpression)
3459+
return leftmostExpr(of: ternaryExpr.conditionExpression, ifMatching: predicate)
34203460
case .functionCallExpr(let functionCallExpr):
3421-
return leftmostMultilineStringLiteral(of: functionCallExpr.calledExpression)
3461+
return leftmostExpr(of: functionCallExpr.calledExpression, ifMatching: predicate)
34223462
case .subscriptExpr(let subscriptExpr):
3423-
return leftmostMultilineStringLiteral(of: subscriptExpr.calledExpression)
3463+
return leftmostExpr(of: subscriptExpr.calledExpression, ifMatching: predicate)
34243464
case .memberAccessExpr(let memberAccessExpr):
3425-
return memberAccessExpr.base.flatMap { leftmostMultilineStringLiteral(of: $0) }
3465+
return memberAccessExpr.base.flatMap { leftmostExpr(of: $0, ifMatching: predicate) }
34263466
case .postfixIfConfigExpr(let postfixIfConfigExpr):
3427-
return postfixIfConfigExpr.base.flatMap { leftmostMultilineStringLiteral(of: $0) }
3467+
return postfixIfConfigExpr.base.flatMap { leftmostExpr(of: $0, ifMatching: predicate) }
34283468
default:
34293469
return nil
34303470
}
34313471
}
34323472

3473+
/// Walks the expression and returns the leftmost multiline string literal (which might be the
3474+
/// expression itself) if the leftmost child is a multiline string literal or if it is a unary
3475+
/// operation applied to a multiline string literal.
3476+
///
3477+
/// - Parameter expr: The expression whose leftmost multiline string literal should be returned.
3478+
/// - Returns: The leftmost multiline string literal, or nil if the leftmost subexpression was
3479+
/// not a multiline string literal.
3480+
private func leftmostMultilineStringLiteral(of expr: ExprSyntax) -> StringLiteralExprSyntax? {
3481+
return leftmostExpr(of: expr) {
3482+
$0.as(StringLiteralExprSyntax.self)?.openQuote.tokenKind == .multilineStringQuote
3483+
}?.as(StringLiteralExprSyntax.self)
3484+
}
3485+
34333486
/// Returns the outermost node enclosing the given node whose closing delimiter(s) must be kept
34343487
/// alongside the last token of the given node. Any tokens between `node.lastToken` and the
34353488
/// returned node's `lastToken` are delimiter tokens that shouldn't be preceded by a break.
@@ -3520,7 +3573,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
35203573
unindentingNode: unindentingParenExpr,
35213574
shouldReset: true,
35223575
breakKind: .continuation,
3523-
shouldGroup: true
3576+
shouldGroup: false
35243577
)
35253578
}
35263579

@@ -3536,7 +3589,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
35363589
unindentingNode: Syntax(parenthesizedExpr),
35373590
shouldReset: false,
35383591
breakKind: .continuation,
3539-
shouldGroup: true
3592+
shouldGroup: false
35403593
)
35413594
}
35423595

@@ -3552,6 +3605,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
35523605
)
35533606
}
35543607

3608+
if let leftmostExpr = leftmostExpr(of: rhs, ifMatching: {
3609+
$0.is(IfExprSyntax.self) || $0.is(SwitchExprSyntax.self)
3610+
}) {
3611+
return (
3612+
unindentingNode: Syntax(leftmostExpr),
3613+
shouldReset: false,
3614+
breakKind: .block,
3615+
shouldGroup: true
3616+
)
3617+
}
3618+
35553619
// Otherwise, don't stack--use regular continuation breaks instead.
35563620
return nil
35573621
}

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)