Skip to content

Commit c01f9a8

Browse files
authored
Merge pull request swiftlang#108 from allevato/nested-ternaries
Vastly improve formatting of ternary expressions.
2 parents 619f89b + fa8a09f commit c01f9a8

File tree

3 files changed

+182
-8
lines changed

3 files changed

+182
-8
lines changed

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,11 +1256,19 @@ private final class TokenStreamCreator: SyntaxVisitor {
12561256
}
12571257

12581258
func visit(_ node: TernaryExprSyntax) -> SyntaxVisitorContinueKind {
1259-
before(node.questionMark, tokens: .break, .open)
1259+
// The order of the .open/.close tokens here is intentional. They are normally paired with the
1260+
// corresponding breaks, but in this case, we want to prioritize keeping the entire `? a : b`
1261+
// part together if some part of the ternary wraps, instead of keeping `c ? a` together and
1262+
// wrapping after that.
1263+
before(node.questionMark, tokens: .break(.open(kind: .continuation)), .open)
12601264
after(node.questionMark, tokens: .space)
1261-
before(node.colonMark, tokens: .break, .open)
1265+
before(
1266+
node.colonMark,
1267+
tokens: .break(.close(mustBreak: false), size: 0), .break(.open(kind: .continuation)), .open)
12621268
after(node.colonMark, tokens: .space)
1263-
after(node.secondChoice.lastToken, tokens: .close, .close)
1269+
after(
1270+
node.secondChoice.lastToken,
1271+
tokens: .break(.close(mustBreak: false), size: 0), .close, .close)
12641272
return .visitChildren
12651273
}
12661274

@@ -2540,6 +2548,8 @@ private final class TokenStreamCreator: SyntaxVisitor {
25402548
switch expr {
25412549
case let sequenceExpr as SequenceExprSyntax:
25422550
return sequenceExpr.elements.count > 1
2551+
case is TernaryExprSyntax:
2552+
return true
25432553
case let tryExpr as TryExprSyntax:
25442554
return isCompoundExpression(tryExpr.expression)
25452555
case let tupleExpr as TupleExprSyntax where tupleExpr.elementList.count == 1:
@@ -2619,6 +2629,12 @@ private final class TokenStreamCreator: SyntaxVisitor {
26192629
}
26202630
}
26212631

2632+
// If the right-hand-side is a ternary expression, stack indentation around the condition so
2633+
// that it is indented relative to the `?` and `:` tokens.
2634+
if let ternaryExpr = rhs as? TernaryExprSyntax {
2635+
return (unindentingNode: ternaryExpr.conditionExpression, shouldReset: false)
2636+
}
2637+
26222638
// If the right-hand-side of the operator is or starts with a parenthesized expression, stack
26232639
// indentation around the operator and those parentheses. We don't need to reset here because
26242640
// the parentheses are sufficient to provide a visual indication of the nesting relationship.

Tests/SwiftFormatPrettyPrintTests/TernaryExprTests.swift

Lines changed: 160 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@ public class TernaryExprTests: PrettyPrintTestCase {
1616
let x = a ? b : c
1717
let y = a ? b : c
1818
let z = a ? b : c
19-
let reallyLongName = a
20-
? longTruePart : longFalsePart
21-
let reallyLongName = reallyLongCondition
19+
let reallyLongName =
20+
a ? longTruePart : longFalsePart
21+
let reallyLongName =
22+
reallyLongCondition
2223
? reallyLongTruePart : reallyLongFalsePart
23-
let reallyLongName = reallyLongCondition
24+
let reallyLongName =
25+
reallyLongCondition
2426
? reallyReallyReallyLongTruePart
2527
: reallyLongFalsePart
26-
let reallyLongName = someCondition
28+
let reallyLongName =
29+
someCondition
2730
? firstFunc(foo: arg)
2831
: secondFunc(bar: arg)
2932
@@ -52,4 +55,156 @@ public class TernaryExprTests: PrettyPrintTestCase {
5255

5356
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
5457
}
58+
59+
public func testTernaryWithWrappingExpressions() {
60+
let input =
61+
"""
62+
foo = firstTerm + secondTerm + thirdTerm ? firstTerm + secondTerm + thirdTerm : firstTerm + secondTerm + thirdTerm
63+
let foo = firstTerm + secondTerm + thirdTerm ? firstTerm + secondTerm + thirdTerm : firstTerm + secondTerm + thirdTerm
64+
foo = firstTerm || secondTerm && thirdTerm ? firstTerm + secondTerm + thirdTerm : firstTerm + secondTerm + thirdTerm
65+
let foo = firstTerm || secondTerm && thirdTerm ? firstTerm + secondTerm + thirdTerm : firstTerm + secondTerm + thirdTerm
66+
"""
67+
68+
let expected =
69+
"""
70+
foo =
71+
firstTerm
72+
+ secondTerm
73+
+ thirdTerm
74+
? firstTerm
75+
+ secondTerm
76+
+ thirdTerm
77+
: firstTerm
78+
+ secondTerm
79+
+ thirdTerm
80+
let foo =
81+
firstTerm
82+
+ secondTerm
83+
+ thirdTerm
84+
? firstTerm
85+
+ secondTerm
86+
+ thirdTerm
87+
: firstTerm
88+
+ secondTerm
89+
+ thirdTerm
90+
foo =
91+
firstTerm
92+
|| secondTerm
93+
&& thirdTerm
94+
? firstTerm
95+
+ secondTerm
96+
+ thirdTerm
97+
: firstTerm
98+
+ secondTerm
99+
+ thirdTerm
100+
let foo =
101+
firstTerm
102+
|| secondTerm
103+
&& thirdTerm
104+
? firstTerm
105+
+ secondTerm
106+
+ thirdTerm
107+
: firstTerm
108+
+ secondTerm
109+
+ thirdTerm
110+
111+
"""
112+
113+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 15)
114+
}
115+
116+
public func testNestedTernaries() {
117+
let input =
118+
"""
119+
a = b ? c : d ? e : f
120+
let a = b ? c : d ? e : f
121+
a = b ? c0 + c1 : d ? e : f
122+
let a = b ? c0 + c1 : d ? e : f
123+
a = b ? c0 + c1 + c2 + c3 : d ? e : f
124+
let a = b ? c0 + c1 + c2 + c3 : d ? e : f
125+
foo = testA ? testB ? testC : testD : testE ? testF : testG
126+
let foo = testA ? testB ? testC : testD : testE ? testF : testG
127+
"""
128+
129+
let expected =
130+
"""
131+
a =
132+
b
133+
? c
134+
: d ? e : f
135+
let a =
136+
b
137+
? c
138+
: d ? e : f
139+
a =
140+
b
141+
? c0 + c1
142+
: d ? e : f
143+
let a =
144+
b
145+
? c0 + c1
146+
: d ? e : f
147+
a =
148+
b
149+
? c0 + c1
150+
+ c2 + c3
151+
: d ? e : f
152+
let a =
153+
b
154+
? c0 + c1
155+
+ c2 + c3
156+
: d ? e : f
157+
foo =
158+
testA
159+
? testB
160+
? testC
161+
: testD
162+
: testE
163+
? testF
164+
: testG
165+
let foo =
166+
testA
167+
? testB
168+
? testC
169+
: testD
170+
: testE
171+
? testF
172+
: testG
173+
174+
"""
175+
176+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 15)
177+
}
178+
179+
public func testExpressionStartsWithTernary() {
180+
// When the ternary itself doesn't already start on a continuation line, we don't have a way
181+
// to indent the continuation of the condition differently from the first and second choices,
182+
// because we don't want to double-indent the condition's continuation lines, and we don't want
183+
// to keep put the choices at the same indentation level as the condition (because that would
184+
// be the start of the statement). Neither of these choices is really ideal, unfortunately.
185+
let input =
186+
"""
187+
condition ? callSomething() : callSomethingElse()
188+
condition && otherCondition ? callSomething() : callSomethingElse()
189+
(condition && otherCondition) ? callSomething() : callSomethingElse()
190+
"""
191+
192+
let expected =
193+
"""
194+
condition
195+
? callSomething()
196+
: callSomethingElse()
197+
condition
198+
&& otherCondition
199+
? callSomething()
200+
: callSomethingElse()
201+
(condition
202+
&& otherCondition)
203+
? callSomething()
204+
: callSomethingElse()
205+
206+
"""
207+
208+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 25)
209+
}
55210
}

Tests/SwiftFormatPrettyPrintTests/XCTestManifests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,11 @@ extension TernaryExprTests {
632632
// `swift test --generate-linuxmain`
633633
// to regenerate.
634634
static let __allTests__TernaryExprTests = [
635+
("testExpressionStartsWithTernary", testExpressionStartsWithTernary),
636+
("testNestedTernaries", testNestedTernaries),
635637
("testTernaryExprs", testTernaryExprs),
636638
("testTernaryExprsWithMultiplePartChoices", testTernaryExprsWithMultiplePartChoices),
639+
("testTernaryWithWrappingExpressions", testTernaryWithWrappingExpressions),
637640
]
638641
}
639642

0 commit comments

Comments
 (0)