Skip to content

Commit e0893d9

Browse files
committed
Group the try keyword with a relevant portion of its expression.
This keeps the try keyword and a portion of the expression that may throw on the same line, when possible. Previously, the group around certain member access exprs would result in frequently breaking after the keyword. That group is replaced by a group that starts at the try keyword now instead.
1 parent d9ac67e commit e0893d9

File tree

2 files changed

+81
-2
lines changed

2 files changed

+81
-2
lines changed

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -880,8 +880,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
880880

881881
if let calledMemberAccessExpr = node.calledExpression.as(MemberAccessExprSyntax.self) {
882882
if let base = calledMemberAccessExpr.base, base.is(IdentifierExprSyntax.self) {
883-
before(base.firstToken, tokens: .open)
884-
after(calledMemberAccessExpr.name.lastToken, tokens: .close)
883+
// When this function call is wrapped by a try-expr, the group applied when visiting the
884+
// try-expr is sufficient. Adding another gruop here in that case can result in
885+
// unnecessarily breaking after the try keyword.
886+
if !(base.firstToken?.previousToken?.parent?.is(TryExprSyntax.self) ?? false) {
887+
before(base.firstToken, tokens: .open)
888+
after(calledMemberAccessExpr.name.lastToken, tokens: .close)
889+
}
885890
}
886891
}
887892

@@ -1416,9 +1421,41 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
14161421

14171422
override func visit(_ node: TryExprSyntax) -> SyntaxVisitorContinueKind {
14181423
before(node.expression.firstToken, tokens: .break)
1424+
1425+
// Check for an anchor token inside of the expression to group with the try keyword.
1426+
if let anchorToken = findTryExprConnectingToken(inExpr: node.expression) {
1427+
before(node.tryKeyword, tokens: .open)
1428+
after(anchorToken, tokens: .close)
1429+
}
1430+
14191431
return .visitChildren
14201432
}
14211433

1434+
/// Searches the AST from `expr` to find a token that should be grouped with an enclosing
1435+
/// try-expr. Returns that token, or nil when no such token is found.
1436+
///
1437+
/// - Parameter expr: An expression that is wrapped by a try-expr.
1438+
/// - Returns: A token that should be grouped with the try-expr, or nil.
1439+
func findTryExprConnectingToken(inExpr expr: ExprSyntax) -> TokenSyntax? {
1440+
if let callingExpr = expr.asProtocol(CallingExprSyntaxProtocol.self) {
1441+
return findTryExprConnectingToken(inExpr: callingExpr.calledExpression)
1442+
}
1443+
if let memberAccessExpr = expr.as(MemberAccessExprSyntax.self), let base = memberAccessExpr.base
1444+
{
1445+
// When there's a simple base (i.e. identifier), group the entire `try <base>.<name>`
1446+
// sequence. This check has to happen here so that the `MemberAccessExprSyntax.name` is
1447+
// available.
1448+
if base.is(IdentifierExprSyntax.self) {
1449+
return memberAccessExpr.name.lastToken
1450+
}
1451+
return findTryExprConnectingToken(inExpr: base)
1452+
}
1453+
if expr.is(IdentifierExprSyntax.self) {
1454+
return expr.lastToken
1455+
}
1456+
return nil
1457+
}
1458+
14221459
override func visit(_ node: TypeExprSyntax) -> SyntaxVisitorContinueKind {
14231460
return .visitChildren
14241461
}

Tests/SwiftFormatPrettyPrintTests/TryCatchTests.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,46 @@ final class TryCatchTests: PrettyPrintTestCase {
231231

232232
assertPrettyPrintEqual(input: input, expected: expected, linelength: 25)
233233
}
234+
235+
func testTryKeywordBreaking() {
236+
let input =
237+
"""
238+
let aVeryLongArgumentName = try foo.bar()
239+
let aVeryLongArgumentName = try? foo.bar()
240+
let abc = try foo.baz().quxxe(a, b, c).bar()
241+
let abc = try foo
242+
.baz().quxxe(a, b, c).bar()
243+
let abc = try [1, 2, 3, 4, 5, 6, 7].baz().quxxe(a, b, c).bar()
244+
let abc = try [1, 2, 3, 4, 5, 6, 7]
245+
.baz().quxxe(a, b, c).bar()
246+
let abc = try foo.baz().quxxe(a, b, c).bar[0]
247+
let abc = try foo
248+
.baz().quxxe(a, b, c).bar[0]
249+
"""
250+
251+
let expected =
252+
"""
253+
let aVeryLongArgumentName =
254+
try foo.bar()
255+
let aVeryLongArgumentName =
256+
try? foo.bar()
257+
let abc = try foo.baz().quxxe(a, b, c)
258+
.bar()
259+
let abc =
260+
try foo
261+
.baz().quxxe(a, b, c).bar()
262+
let abc = try [1, 2, 3, 4, 5, 6, 7]
263+
.baz().quxxe(a, b, c).bar()
264+
let abc = try [1, 2, 3, 4, 5, 6, 7]
265+
.baz().quxxe(a, b, c).bar()
266+
let abc = try foo.baz().quxxe(a, b, c)
267+
.bar[0]
268+
let abc =
269+
try foo
270+
.baz().quxxe(a, b, c).bar[0]
271+
272+
"""
273+
274+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 40)
275+
}
234276
}

0 commit comments

Comments
 (0)