Skip to content

Commit 9d8bfe4

Browse files
committed
Fix scrunching of range operators.
We can't unconditionally remove all whitespace around range operators when they are preceded by a postfix operator or followed by a postfix operator; the parser greedily treats the combined string as a single operator. We still remove whitespace around range operators when possible, but rather than try to strictly enforce the lack of whitespace in all cases through more clever and potentially disruptive transformations (like surrounding the operands by parentheses), we just keep the whitespace when we can't remove it. Fixes SR-11798.
1 parent eb5fcfa commit 9d8bfe4

File tree

3 files changed

+166
-6
lines changed

3 files changed

+166
-6
lines changed

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2445,13 +2445,24 @@ private final class TokenStreamCreator: SyntaxVisitor {
24452445
// The token kind (spaced or unspaced operator) represents how the *user* wrote it, and we want
24462446
// to ignore that and apply our own rules.
24472447
if let binaryOperator = operatorExpr as? BinaryOperatorExprSyntax {
2448-
let operatorText = binaryOperator.operatorToken.text
2449-
if let precedence = operatorContext.infixOperator(named: operatorText)?.precedenceGroup,
2448+
let token = binaryOperator.operatorToken
2449+
if let precedence = operatorContext.infixOperator(named: token.text)?.precedenceGroup,
24502450
precedence === operatorContext.precedenceGroup(named: .rangeFormation)
24512451
{
2452+
// We want to omit whitespace around range formation operators if possible. We can't do this
2453+
// if the token is either preceded by a postfix operator or followed by a prefix operator---
2454+
// removing the spaces in those situations would cause the parser to greedily treat the
2455+
// combined sequence of operator characters as a single operator.
2456+
if case .postfixOperator? = token.previousToken?.tokenKind { return true }
2457+
if case .prefixOperator? = token.nextToken?.tokenKind { return true }
24522458
return false
24532459
}
24542460
}
2461+
2462+
// For all other operators, we want to require whitespace on each side. That's always safe, so
2463+
// we don't need to be concerned about neighboring operator tokens. For example, we don't need
2464+
// to be concerned about the user writing "4+-5" when they meant "4 + -5", because Swift would
2465+
// always parse the former as "4 +- 5".
24552466
return true
24562467
}
24572468
}
Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,161 @@
11
public class BinaryOperatorExprTests: PrettyPrintTestCase {
2-
public func testOperatorSpacing() {
2+
3+
public func testNonRangeFormationOperatorsAreSurroundedByBreaks() {
4+
let input =
5+
"""
6+
x=1+8-9 ^*^ 5*4/10
7+
"""
8+
9+
let expected80 =
10+
"""
11+
x = 1 + 8 - 9 ^*^ 5 * 4 / 10
12+
13+
"""
14+
15+
assertPrettyPrintEqual(input: input, expected: expected80, linelength: 80)
16+
17+
let expected10 =
18+
"""
19+
x = 1 + 8
20+
- 9
21+
^*^ 5
22+
* 4 / 10
23+
24+
"""
25+
26+
assertPrettyPrintEqual(input: input, expected: expected10, linelength: 10)
27+
}
28+
29+
public func testRangeFormationOperatorsAreCompactedWhenPossible() {
330
let input =
431
"""
5-
x=1+8-9 ..< 5*4/10
32+
x = 1...100
33+
x = 1..<100
34+
x = (1++)...(-100)
35+
x = 1 ... 100
36+
x = 1 ..< 100
37+
x = (1++) ... (-100)
638
"""
739

840
let expected =
941
"""
10-
x = 1 + 8 - 9..<5 * 4 / 10
42+
x = 1...100
43+
x = 1..<100
44+
x = (1++)...(-100)
45+
x = 1...100
46+
x = 1..<100
47+
x = (1++)...(-100)
1148
1249
"""
1350

1451
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
1552
}
53+
54+
public func testRangeFormationOperatorsAreNotCompactedWhenFollowingAPostfixOperator() {
55+
let input =
56+
"""
57+
x = 1++ ... 100
58+
x = 1-- ..< 100
59+
x = 1++ ... 100
60+
x = 1-- ..< 100
61+
"""
62+
63+
let expected80 =
64+
"""
65+
x = 1++ ... 100
66+
x = 1-- ..< 100
67+
x = 1++ ... 100
68+
x = 1-- ..< 100
69+
70+
"""
71+
72+
assertPrettyPrintEqual(input: input, expected: expected80, linelength: 80)
73+
74+
let expected10 =
75+
"""
76+
x = 1++
77+
... 100
78+
x = 1--
79+
..< 100
80+
x = 1++
81+
... 100
82+
x = 1--
83+
..< 100
84+
85+
"""
86+
87+
assertPrettyPrintEqual(input: input, expected: expected10, linelength: 10)
88+
}
89+
90+
public func testRangeFormationOperatorsAreNotCompactedWhenPrecedingAPrefixOperator() {
91+
let input =
92+
"""
93+
x = 1 ... -100
94+
x = 1 ..< -100
95+
x = 1 ... √100
96+
x = 1 ..< √100
97+
"""
98+
99+
let expected80 =
100+
"""
101+
x = 1 ... -100
102+
x = 1 ..< -100
103+
x = 1 ... √100
104+
x = 1 ..< √100
105+
106+
"""
107+
108+
assertPrettyPrintEqual(input: input, expected: expected80, linelength: 80)
109+
110+
let expected10 =
111+
"""
112+
x = 1
113+
... -100
114+
x = 1
115+
..< -100
116+
x = 1
117+
... √100
118+
x = 1
119+
..< √100
120+
121+
"""
122+
123+
assertPrettyPrintEqual(input: input, expected: expected10, linelength: 10)
124+
}
125+
126+
public func testRangeFormationOperatorsAreNotCompactedWhenUnaryOperatorsAreOnEachSide() {
127+
let input =
128+
"""
129+
x = 1++ ... -100
130+
x = 1-- ..< -100
131+
x = 1++ ... √100
132+
x = 1-- ..< √100
133+
"""
134+
135+
let expected80 =
136+
"""
137+
x = 1++ ... -100
138+
x = 1-- ..< -100
139+
x = 1++ ... √100
140+
x = 1-- ..< √100
141+
142+
"""
143+
144+
assertPrettyPrintEqual(input: input, expected: expected80, linelength: 80)
145+
146+
let expected10 =
147+
"""
148+
x = 1++
149+
... -100
150+
x = 1--
151+
..< -100
152+
x = 1++
153+
... √100
154+
x = 1--
155+
..< √100
156+
157+
"""
158+
159+
assertPrettyPrintEqual(input: input, expected: expected10, linelength: 10)
160+
}
16161
}

Tests/SwiftFormatPrettyPrintTests/XCTestManifests.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,11 @@ extension BinaryOperatorExprTests {
8989
// `swift test --generate-linuxmain`
9090
// to regenerate.
9191
static let __allTests__BinaryOperatorExprTests = [
92-
("testOperatorSpacing", testOperatorSpacing),
92+
("testNonRangeFormationOperatorsAreSurroundedByBreaks", testNonRangeFormationOperatorsAreSurroundedByBreaks),
93+
("testRangeFormationOperatorsAreCompactedWhenPossible", testRangeFormationOperatorsAreCompactedWhenPossible),
94+
("testRangeFormationOperatorsAreNotCompactedWhenFollowingAPostfixOperator", testRangeFormationOperatorsAreNotCompactedWhenFollowingAPostfixOperator),
95+
("testRangeFormationOperatorsAreNotCompactedWhenPrecedingAPrefixOperator", testRangeFormationOperatorsAreNotCompactedWhenPrecedingAPrefixOperator),
96+
("testRangeFormationOperatorsAreNotCompactedWhenUnaryOperatorsAreOnEachSide", testRangeFormationOperatorsAreNotCompactedWhenUnaryOperatorsAreOnEachSide),
9397
]
9498
}
9599

0 commit comments

Comments
 (0)