Skip to content

Commit 9ab8329

Browse files
committed
Merge pull request #545 from allevato/keypath-wrapping
Wrap keypath literals appropriately.
1 parent 4c612e2 commit 9ab8329

File tree

2 files changed

+92
-28
lines changed

2 files changed

+92
-28
lines changed

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,30 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
17591759
}
17601760

17611761
override func visit(_ node: KeyPathExprSyntax) -> SyntaxVisitorContinueKind {
1762+
before(node.backslash, tokens: .open)
1763+
after(node.lastToken(viewMode: .sourceAccurate), tokens: .close)
1764+
return .visitChildren
1765+
}
1766+
1767+
override func visit(_ node: KeyPathComponentSyntax) -> SyntaxVisitorContinueKind {
1768+
// If this is the first component (immediately after the backslash), allow a break after the
1769+
// slash only if a typename follows it. Do not break in the middle of `\.`.
1770+
var breakBeforePeriod = true
1771+
if let keyPathComponents = node.parent?.as(KeyPathComponentListSyntax.self),
1772+
let keyPathExpr = keyPathComponents.parent?.as(KeyPathExprSyntax.self),
1773+
node == keyPathExpr.components.first, keyPathExpr.root == nil
1774+
{
1775+
breakBeforePeriod = false
1776+
}
1777+
if breakBeforePeriod {
1778+
before(node.period, tokens: .break(.continue, size: 0))
1779+
}
1780+
return .visitChildren
1781+
}
1782+
1783+
override func visit(_ node: KeyPathSubscriptComponentSyntax) -> SyntaxVisitorContinueKind {
1784+
after(node.leftBracket, tokens: .break(.open, size: 0), .open)
1785+
before(node.rightBracket, tokens: .break(.close, size: 0), .close)
17621786
return .visitChildren
17631787
}
17641788

@@ -1846,24 +1870,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
18461870
}
18471871

18481872
override func visit(_ node: InfixOperatorExprSyntax) -> SyntaxVisitorContinueKind {
1849-
// FIXME: This is a workaround/hack for https://github.com/apple/swift-syntax/issues/928. For
1850-
// keypaths like `\.?.foo`, they get represented (after folding) as an infix operator expression
1851-
// with an empty keypath, followed by the "binary operator" `.?.`, followed by other
1852-
// expressions. We can detect this and treat the whole thing as a verbatim node, which mimics
1853-
// what we do today for keypaths (i.e., nothing).
1854-
if let keyPathExpr = node.leftOperand.as(KeyPathExprSyntax.self),
1855-
keyPathExpr.components.isEmpty
1856-
{
1857-
// If there were spaces in the trailing trivia of the previous token, they would have been
1858-
// ignored (since this expression would be expected to insert its own preceding breaks).
1859-
// Preserve that whitespace verbatim for now.
1860-
if let previousToken = node.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .sourceAccurate) {
1861-
appendTrailingTrivia(previousToken, forced: true)
1862-
}
1863-
verbatimToken(Syntax(node), indentingBehavior: .none)
1864-
return .skipChildren
1865-
}
1866-
18671873
let binOp = node.operatorOperand
18681874
if binOp.is(ArrowExprSyntax.self) {
18691875
// `ArrowExprSyntax` nodes occur when a function type is written in an expression context;

Tests/SwiftFormatPrettyPrintTests/KeyPathExprTests.swift

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// TODO: Add more tests and figure out how we want to wrap keypaths. Right now, they just get
2-
// printed without breaks.
31
final class KeyPathExprTests: PrettyPrintTestCase {
42
func testSimple() {
53
let input =
@@ -47,15 +45,31 @@ final class KeyPathExprTests: PrettyPrintTestCase {
4745
let z = a.map(\.foo!.bar)
4846
"""#
4947

50-
let expected =
48+
let expected80 =
5149
#"""
5250
let x = \.foo?
5351
let y = \.foo!.bar
5452
let z = a.map(\.foo!.bar)
5553
5654
"""#
5755

58-
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
56+
assertPrettyPrintEqual(input: input, expected: expected80, linelength: 80)
57+
58+
let expected11 =
59+
#"""
60+
let x =
61+
\.foo?
62+
let y =
63+
\.foo!
64+
.bar
65+
let z =
66+
a.map(
67+
\.foo!
68+
.bar)
69+
70+
"""#
71+
72+
assertPrettyPrintEqual(input: input, expected: expected11, linelength: 11)
5973
}
6074

6175
func testSubscript() {
@@ -80,19 +94,63 @@ final class KeyPathExprTests: PrettyPrintTestCase {
8094
func testImplicitSelfUnwrap() {
8195
let input =
8296
#"""
83-
//let x = \.?.foo
84-
//let y = \.?.foo.bar
97+
let x = \.?.foo
98+
let y = \.?.foo.bar
8599
let z = a.map(\.?.foo.bar)
86100
"""#
87101

88-
let expected =
102+
let expected80 =
89103
#"""
90-
//let x = \.?.foo
91-
//let y = \.?.foo.bar
104+
let x = \.?.foo
105+
let y = \.?.foo.bar
92106
let z = a.map(\.?.foo.bar)
93107
94108
"""#
95109

96-
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
110+
assertPrettyPrintEqual(input: input, expected: expected80, linelength: 80)
111+
112+
let expected11 =
113+
#"""
114+
let x =
115+
\.?.foo
116+
let y =
117+
\.?.foo
118+
.bar
119+
let z =
120+
a.map(
121+
\.?.foo
122+
.bar)
123+
124+
"""#
125+
126+
assertPrettyPrintEqual(input: input, expected: expected11, linelength: 11)
127+
}
128+
129+
func testWrapping() {
130+
let input =
131+
#"""
132+
let x = \ReallyLongType.reallyLongProperty.anotherLongProperty
133+
let x = \.reeeeallyLongProperty.anotherLongProperty
134+
let x = \.longProperty.a.b.c[really + long + expression].anotherLongProperty
135+
"""#
136+
137+
let expected =
138+
#"""
139+
let x =
140+
\ReallyLongType
141+
.reallyLongProperty
142+
.anotherLongProperty
143+
let x =
144+
\.reeeeallyLongProperty
145+
.anotherLongProperty
146+
let x =
147+
\.longProperty.a.b.c[
148+
really + long
149+
+ expression
150+
].anotherLongProperty
151+
152+
"""#
153+
154+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 23)
97155
}
98156
}

0 commit comments

Comments
 (0)