@@ -601,13 +601,26 @@ private final class TokenStreamCreator: SyntaxVisitor {
601
601
}
602
602
603
603
func visit( _ node: TupleExprSyntax ) -> SyntaxVisitorContinueKind {
604
- after ( node. leftParen, tokens: . break( . open, size: 0 ) , . open)
605
- before ( node. rightParen, tokens: . close, . break( . close, size: 0 ) )
606
-
607
- insertTokens ( . break( . same) , betweenElementsOf: node. elementList)
608
-
609
- for element in node. elementList {
610
- arrangeAsTupleExprElement ( element)
604
+ // We'll do nothing if it's a zero-element tuple, because we just want to keep the empty `()`
605
+ // together.
606
+ let elementCount = node. elementList. count
607
+
608
+ if elementCount == 1 {
609
+ // A tuple with one element is a parenthesized expression; add a group around it to keep it
610
+ // together when possible, but breaks are handled elsewhere (see calls to
611
+ // `stackedIndentationBehavior`).
612
+ after ( node. leftParen, tokens: . open)
613
+ before ( node. rightParen, tokens: . close)
614
+ } else if elementCount > 1 {
615
+ // Tuples with more than one element are "true" tuples, and should indent as block structures.
616
+ after ( node. leftParen, tokens: . break( . open, size: 0 ) , . open)
617
+ before ( node. rightParen, tokens: . close, . break( . close, size: 0 ) )
618
+
619
+ insertTokens ( . break( . same) , betweenElementsOf: node. elementList)
620
+
621
+ for element in node. elementList {
622
+ arrangeAsTupleExprElement ( element)
623
+ }
611
624
}
612
625
613
626
return . visitChildren
@@ -1296,35 +1309,47 @@ private final class TokenStreamCreator: SyntaxVisitor {
1296
1309
1297
1310
if shouldRequireWhitespace ( around: binOp) {
1298
1311
if isAssigningOperator ( binOp) {
1299
- var beforeTokens : [ Token ] = [ . break( . open( kind: . continuation) ) ]
1300
- var afterTokens : [ Token ] = [ . break( . close( mustBreak: false ) , size: 0 ) ]
1312
+ var beforeTokens : [ Token ]
1313
+
1314
+ // If the rhs starts with a parenthesized expression, stack indentation around it.
1315
+ // Otherwise, use regular continuation breaks.
1316
+ if let ( unindentingNode, _) = stackedIndentationBehavior ( after: binOp, rhs: rhs) {
1317
+ beforeTokens = [ . break( . open( kind: . continuation) ) ]
1318
+ after ( unindentingNode. lastToken, tokens: [ . break( . close( mustBreak: false ) , size: 0 ) ] )
1319
+ } else {
1320
+ beforeTokens = [ . break( . continue) ]
1321
+ }
1301
1322
1302
1323
// When the RHS is a simple expression, even if is requires multiple lines, we don't add a
1303
1324
// group so that as much of the expression as possible can stay on the same line as the
1304
1325
// operator token.
1305
1326
if isCompoundExpression ( rhs) {
1306
1327
beforeTokens. append ( . open)
1307
- afterTokens . append ( . close)
1328
+ after ( rhs . lastToken , tokens : . close)
1308
1329
}
1330
+
1309
1331
after ( binOp. lastToken, tokens: beforeTokens)
1310
- after ( rhs. lastToken, tokens: afterTokens)
1311
- } else if shouldStackIndentation ( after: binOp) {
1312
- // For certain operators like `&&` and `||`, we don't want to treat all continue breaks
1313
- // the same. If we did, then all operators would line up at the same alignment regardless
1314
- // of whether they were, for example, `&&` or something between a pair of `&&`. To make
1315
- // long conditionals format more cleanly, we use open-continuation/close pairs around such
1316
- // operators and their right-hand sides so that the continuation breaks inside those
1317
- // scopes "stack", instead of receiving the usual single-level "continuation line or not"
1318
- // behavior.
1332
+ } else if let ( unindentingNode, shouldReset) =
1333
+ stackedIndentationBehavior ( after: binOp, rhs: rhs)
1334
+ {
1335
+ // For parenthesized expressions and for unparenthesized usages of `&&` and `||`, we don't
1336
+ // want to treat all continue breaks the same. If we did, then all operators would line up
1337
+ // at the same alignment regardless of whether they were, for example, `&&` or something
1338
+ // between a pair of `&&`. To make long expressions/conditionals format more cleanly, we
1339
+ // use open-continuation/close pairs around such operators and their right-hand sides so
1340
+ // that the continuation breaks inside those scopes "stack", instead of receiving the
1341
+ // usual single-level "continuation line or not" behavior.
1319
1342
let openBreakTokens : [ Token ] = [ . break( . open( kind: . continuation) ) , . open]
1320
1343
if wrapsBeforeOperator {
1321
1344
before ( binOp. firstToken, tokens: openBreakTokens)
1322
1345
} else {
1323
1346
after ( binOp. lastToken, tokens: openBreakTokens)
1324
1347
}
1325
- after (
1326
- rhs. lastToken,
1327
- tokens: [ . break( . reset, size: 0 ) , . break( . close( mustBreak: false ) , size: 0 ) , . close] )
1348
+
1349
+ let closeBreakTokens : [ Token ] =
1350
+ ( shouldReset ? [ . break( . reset, size: 0 ) ] : [ ] )
1351
+ + [ . break( . close( mustBreak: false ) , size: 0 ) , . close]
1352
+ after ( unindentingNode. lastToken, tokens: closeBreakTokens)
1328
1353
} else {
1329
1354
if wrapsBeforeOperator {
1330
1355
before ( binOp. firstToken, tokens: . break( . continue) )
@@ -1422,14 +1447,19 @@ private final class TokenStreamCreator: SyntaxVisitor {
1422
1447
closeAfterToken = typeAnnotation. lastToken
1423
1448
}
1424
1449
if let initializer = node. initializer {
1425
- after ( initializer. equal, tokens: . break( . open( kind: . continuation) ) )
1426
- closesNeeded += 1
1450
+ let expr = initializer. value
1451
+
1452
+ if let ( unindentingNode, _) = stackedIndentationBehavior ( rhs: expr) {
1453
+ after ( initializer. equal, tokens: . break( . open( kind: . continuation) ) )
1454
+ after ( unindentingNode. lastToken, tokens: . break( . close( mustBreak: false ) , size: 0 ) )
1455
+ } else {
1456
+ after ( initializer. equal, tokens: . break( . continue) )
1457
+ }
1427
1458
closeAfterToken = initializer. lastToken
1428
1459
1429
1460
// When the RHS is a simple expression, even if is requires multiple lines, we don't add a
1430
1461
// group so that as much of the expression as possible can stay on the same line as the
1431
1462
// operator token.
1432
- let expr = initializer. value
1433
1463
if isCompoundExpression ( expr) {
1434
1464
before ( expr. firstToken, tokens: . open)
1435
1465
after ( expr. lastToken, tokens: . close)
@@ -2474,13 +2504,16 @@ private final class TokenStreamCreator: SyntaxVisitor {
2474
2504
/// that are known to wrap an expressions, e.g. try expressions, are handled by checking the
2475
2505
/// expression that they contain.
2476
2506
private func isCompoundExpression( _ expr: ExprSyntax ) -> Bool {
2477
- if let sequenceExpr = expr as? SequenceExprSyntax {
2507
+ switch expr {
2508
+ case let sequenceExpr as SequenceExprSyntax :
2478
2509
return sequenceExpr. elements. count > 1
2479
- }
2480
- if let tryExpr = expr as? TryExprSyntax {
2510
+ case let tryExpr as TryExprSyntax :
2481
2511
return isCompoundExpression ( tryExpr. expression)
2512
+ case let tupleExpr as TupleExprSyntax where tupleExpr. elementList. count == 1 :
2513
+ return isCompoundExpression ( tupleExpr. elementList. first!. expression)
2514
+ default :
2515
+ return false
2482
2516
}
2483
- return false
2484
2517
}
2485
2518
2486
2519
/// Returns whether the given operator behaves as an assignment, to assign a right-hand-side to a
@@ -2503,24 +2536,65 @@ private final class TokenStreamCreator: SyntaxVisitor {
2503
2536
return false
2504
2537
}
2505
2538
2506
- /// Returns a value indicating whether indentation should be stacked within subexpressions to the
2507
- /// right of the given operator.
2539
+ /// Walks the expression and returns the leftmost subexpression if it is parenthesized (which
2540
+ /// might be the expression itself).
2541
+ ///
2542
+ /// - Parameter expr: The expression whose parenthesized leftmost subexpression should be
2543
+ /// returned.
2544
+ /// - Returns: The parenthesized leftmost subexpression, or nil if the leftmost subexpression was
2545
+ /// not parenthesized.
2546
+ private func parenthesizedLeftmostExpr( of expr: ExprSyntax ) -> TupleExprSyntax ? {
2547
+ switch expr {
2548
+ case let tupleExpr as TupleExprSyntax where tupleExpr. elementList. count == 1 :
2549
+ return tupleExpr
2550
+ case let sequenceExpr as SequenceExprSyntax :
2551
+ return parenthesizedLeftmostExpr ( of: sequenceExpr. elements. first!)
2552
+ case let ternaryExpr as TernaryExprSyntax :
2553
+ return parenthesizedLeftmostExpr ( of: ternaryExpr. conditionExpression)
2554
+ default :
2555
+ return nil
2556
+ }
2557
+ }
2558
+
2559
+ /// Determines if indentation should be stacked around a subexpression to the right of the given
2560
+ /// operator, and, if so, returns the node after which indentation stacking should be closed and
2561
+ /// whether or not the continuation state should be reset as well.
2508
2562
///
2509
- /// Operators that are good candidates for this behavior are ones that have low precedence and
2510
- /// may occur in chains, such as logical AND (`&&`) and OR (`||`) in conditional statements, where
2511
- /// the extra level of indentation helps to improve readability with the operators inside those
2512
- /// conditions.
2513
- private func shouldStackIndentation( after operatorExpr: ExprSyntax ) -> Bool {
2563
+ /// Stacking is applied around parenthesized expressions, but also for low-precedence operators
2564
+ /// that frequently occur in long chains, such as logical AND (`&&`) and OR (`||`) in conditional
2565
+ /// statements. In this case, the extra level of indentation helps to improve readability with the
2566
+ /// operators inside those conditions even when parentheses are not used.
2567
+ private func stackedIndentationBehavior(
2568
+ after operatorExpr: ExprSyntax ? = nil ,
2569
+ rhs: ExprSyntax
2570
+ ) -> ( unindentingNode: ExprSyntax , shouldReset: Bool ) ? {
2571
+ // Check for logical operators first, and if it's that kind of operator, stack indentation
2572
+ // around the entire right-hand-side. We have to do this check before checking the RHS for
2573
+ // parentheses because if the user writes something like `... && (foo) > bar || ...`, we don't
2574
+ // want the indentation stacking that starts before the `&&` to stop after the closing
2575
+ // parenthesis in `(foo)`.
2576
+ //
2577
+ // We also want to reset after undoing the stacked indentation so that we have a visual
2578
+ // indication that the subexpression has ended.
2514
2579
if let binaryOperator = operatorExpr as? BinaryOperatorExprSyntax {
2515
2580
let operatorText = binaryOperator. operatorToken. text
2516
2581
if let precedence = operatorContext. infixOperator ( named: operatorText) ? . precedenceGroup,
2517
2582
precedence === operatorContext. precedenceGroup ( named: . logicalConjunction)
2518
2583
|| precedence === operatorContext. precedenceGroup ( named: . logicalDisjunction)
2519
2584
{
2520
- return true
2585
+ return ( unindentingNode : rhs , shouldReset : true )
2521
2586
}
2522
2587
}
2523
- return false
2588
+
2589
+ // If the right-hand-side of the operator is or starts with a parenthesized expression, stack
2590
+ // indentation around the operator and those parentheses. We don't need to reset here because
2591
+ // the parentheses are sufficient to provide a visual indication of the nesting relationship.
2592
+ if let parenthesizedExpr = parenthesizedLeftmostExpr ( of: rhs) {
2593
+ return ( unindentingNode: parenthesizedExpr, shouldReset: false )
2594
+ }
2595
+
2596
+ // Otherwise, don't stack--use regular continuation breaks instead.
2597
+ return nil
2524
2598
}
2525
2599
2526
2600
/// Returns a value indicating whether whitespace should be required around the given operator.
0 commit comments