Skip to content

Commit 17635f1

Browse files
committed
Add a quick workaround for keypaths starting with \.?..
This can and should be removed once swiftlang/swift-syntax#928 is fixed. This change also adds tests for keypaths (we never had them!) and once the above issue is resolved, we can properly support wrapping for them in general. For now we just print them without any breaks, which is the behavior today (since we don't do any special processing for them).
1 parent 61344e8 commit 17635f1

File tree

2 files changed

+130
-6
lines changed

2 files changed

+130
-6
lines changed

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
135135
}
136136
}
137137

138-
private func verbatimToken(_ node: Syntax) {
138+
private func verbatimToken(_ node: Syntax, indentingBehavior: IndentingBehavior = .allLines) {
139139
if let firstToken = node.firstToken {
140140
appendBeforeTokens(firstToken)
141141
}
142142

143-
appendToken(.verbatim(Verbatim(text: node.description, indentingBehavior: .allLines)))
143+
appendToken(.verbatim(Verbatim(text: node.description, indentingBehavior: indentingBehavior)))
144144

145145
if let lastToken = node.lastToken {
146146
// Extract any comments that trail the verbatim block since they belong to the next syntax
@@ -1769,6 +1769,24 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
17691769
}
17701770

17711771
override func visit(_ node: InfixOperatorExprSyntax) -> SyntaxVisitorContinueKind {
1772+
// FIXME: This is a workaround/hack for https://github.com/apple/swift-syntax/issues/928. For
1773+
// keypaths like `\.?.foo`, they get represented (after folding) as an infix operator expression
1774+
// with an empty keypath, followed by the "binary operator" `.?.`, followed by other
1775+
// expressions. We can detect this and treat the whole thing as a verbatim node, which mimics
1776+
// what we do today for keypaths (i.e., nothing).
1777+
if let keyPathExpr = node.leftOperand.as(KeyPathExprSyntax.self),
1778+
keyPathExpr.components.isEmpty
1779+
{
1780+
// If there were spaces in the trailing trivia of the previous token, they would have been
1781+
// ignored (since this expression would be expected to insert its own preceding breaks).
1782+
// Preserve that whitespace verbatim for now.
1783+
if let previousToken = node.firstToken?.previousToken {
1784+
appendTrailingTrivia(previousToken, forced: true)
1785+
}
1786+
verbatimToken(Syntax(node), indentingBehavior: .none)
1787+
return .skipChildren
1788+
}
1789+
17721790
let binOp = node.operatorOperand
17731791
let rhs = node.rightOperand
17741792
maybeGroupAroundSubexpression(rhs, combiningOperator: binOp)
@@ -2514,14 +2532,22 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
25142532
/// trivia represents a malformed input (as opposed to garbage text in leading trivia, which has
25152533
/// some legitimate uses), this is a reasonable compromise to keep the garbage text roughly in the
25162534
/// same place but still let surrounding formatting occur somewhat as expected.
2517-
private func appendTrailingTrivia(_ token: TokenSyntax) {
2535+
private func appendTrailingTrivia(_ token: TokenSyntax, forced: Bool = false) {
25182536
let trailingTrivia = Array(token.trailingTrivia)
2519-
guard let lastUnexpectedIndex = trailingTrivia.lastIndex(where: { $0.isUnexpectedText }) else {
2520-
return
2537+
let lastIndex: Array<TriviaPiece>.Index
2538+
if forced {
2539+
lastIndex = trailingTrivia.index(before: trailingTrivia.endIndex)
2540+
} else {
2541+
guard
2542+
let lastUnexpectedIndex = trailingTrivia.lastIndex(where: { $0.isUnexpectedText })
2543+
else {
2544+
return
2545+
}
2546+
lastIndex = lastUnexpectedIndex
25212547
}
25222548

25232549
var verbatimText = ""
2524-
for piece in trailingTrivia[...lastUnexpectedIndex] {
2550+
for piece in trailingTrivia[...lastIndex] {
25252551
switch piece {
25262552
case .shebang, .unexpectedText, .spaces, .tabs, .formfeeds, .verticalTabs:
25272553
piece.write(to: &verbatimText)
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// TODO: Add more tests and figure out how we want to wrap keypaths. Right now, they just get
2+
// printed without breaks.
3+
final class KeyPathExprTests: PrettyPrintTestCase {
4+
func testSimple() {
5+
let input =
6+
#"""
7+
let x = \.foo
8+
let y = \.foo.bar
9+
let z = a.map(\.foo.bar)
10+
"""#
11+
12+
let expected =
13+
#"""
14+
let x = \.foo
15+
let y = \.foo.bar
16+
let z = a.map(\.foo.bar)
17+
18+
"""#
19+
20+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
21+
}
22+
23+
func testWithType() {
24+
let input =
25+
#"""
26+
let x = \Type.foo
27+
let y = \Type.foo.bar
28+
let z = a.map(\Type.foo.bar)
29+
"""#
30+
31+
let expected =
32+
#"""
33+
let x = \Type.foo
34+
let y = \Type.foo.bar
35+
let z = a.map(\Type.foo.bar)
36+
37+
"""#
38+
39+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
40+
}
41+
42+
func testOptionalUnwrap() {
43+
let input =
44+
#"""
45+
let x = \.foo?
46+
let y = \.foo!.bar
47+
let z = a.map(\.foo!.bar)
48+
"""#
49+
50+
let expected =
51+
#"""
52+
let x = \.foo?
53+
let y = \.foo!.bar
54+
let z = a.map(\.foo!.bar)
55+
56+
"""#
57+
58+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
59+
}
60+
61+
func testSubscript() {
62+
let input =
63+
#"""
64+
let x = \.foo[0]
65+
let y = \.foo[0].bar
66+
let z = a.map(\.foo[0].bar)
67+
"""#
68+
69+
let expected =
70+
#"""
71+
let x = \.foo[0]
72+
let y = \.foo[0].bar
73+
let z = a.map(\.foo[0].bar)
74+
75+
"""#
76+
77+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
78+
}
79+
80+
func testImplicitSelfUnwrap() {
81+
let input =
82+
#"""
83+
//let x = \.?.foo
84+
//let y = \.?.foo.bar
85+
let z = a.map(\.?.foo.bar)
86+
"""#
87+
88+
let expected =
89+
#"""
90+
//let x = \.?.foo
91+
//let y = \.?.foo.bar
92+
let z = a.map(\.?.foo.bar)
93+
94+
"""#
95+
96+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
97+
}
98+
}

0 commit comments

Comments
 (0)