Skip to content

Commit b2272b2

Browse files
authored
Wrap closing paren of single-argument functions and collections if needed (#2281)
1 parent 11f438b commit b2272b2

File tree

13 files changed

+193
-133
lines changed

13 files changed

+193
-133
lines changed

Snapshots/Issues/645.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,4 @@ let somethingReallyReallyLongJson = a(s: b(status: """
2323
"header": "<h1>",
2424
"div": "<div>"
2525
}
26-
""")
27-
)
26+
"""))

Snapshots/SwiftUI/Bookworm/ContentView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ struct ContentView: View {
2222
List {
2323
ForEach(books, id: \.self) { book in
2424
NavigationLink(destination:
25-
DetailView(book: book).environment(\.managedObjectContext, self.moc)
26-
) {
25+
DetailView(book: book).environment(\.managedObjectContext, self.moc))
26+
{
2727
EmojiRatingView(rating: book.rating)
2828
.font(.largeTitle)
2929

Snapshots/SwiftUI/Moonshot/ContentView.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ struct ContentView: View {
3030
.font(.headline)
3131
Text(self.showCrew ?
3232
self.astronauts(in: mission) :
33-
mission.formattedLaunchDate
34-
)
33+
mission.formattedLaunchDate)
3534
}
3635
}
3736
}

Sources/FormattingHelpers.swift

Lines changed: 95 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,11 @@ extension Formatter {
448448
}
449449
}
450450

451+
let hasLineBreakAfterOpeningParen = nextToken(after: i, where: { !$0.isComment })?.isLinebreak == true
452+
451453
if closingParenOnSameLine {
452454
removeLinebreakBeforeEndOfScope(at: &endOfScope)
453-
} else if insertLinebreakAfterOpeningParen {
455+
} else if hasLineBreakAfterOpeningParen {
454456
// Insert linebreak before closing paren
455457
if let lastIndex = self.index(of: .nonSpace, before: endOfScope) {
456458
endOfScope += insertSpace(indent, at: lastIndex + 1)
@@ -522,6 +524,98 @@ extension Formatter {
522524
)
523525
}
524526

527+
// Wrap nested structures like typealiases and ternary operators first since this may
528+
// prevent the need to do wrap the child expressions.
529+
530+
// -- wraptypealiases
531+
forEach(.keyword("typealias")) { typealiasIndex, _ in
532+
guard options.wrapTypealiases == .beforeFirst || options.wrapTypealiases == .afterFirst,
533+
let (equalsIndex, andTokenIndices, lastIdentifierIndex) = parseProtocolCompositionTypealias(at: typealiasIndex)
534+
else { return }
535+
536+
// Decide which indices to wrap at
537+
// - We always wrap at each `&`
538+
// - For `beforeFirst`, we also wrap before the `=`
539+
let wrapIndices: [Int]
540+
switch options.wrapTypealiases {
541+
case .afterFirst:
542+
wrapIndices = andTokenIndices
543+
case .beforeFirst:
544+
wrapIndices = [equalsIndex] + andTokenIndices
545+
case .default, .disabled, .preserve:
546+
return
547+
}
548+
549+
let didWrap = wrapMultilineStatement(
550+
startIndex: typealiasIndex,
551+
delimiterIndices: wrapIndices,
552+
endIndex: lastIdentifierIndex
553+
)
554+
555+
guard didWrap else { return }
556+
557+
// If we're using `afterFirst` and there was unexpectedly a linebreak
558+
// between the `typealias` and the `=`, we need to remove it
559+
let rangeBetweenTypealiasAndEquals = (typealiasIndex + 1) ..< equalsIndex
560+
if options.wrapTypealiases == .afterFirst,
561+
let linebreakIndex = rangeBetweenTypealiasAndEquals.first(where: { tokens[$0].isLinebreak })
562+
{
563+
removeToken(at: linebreakIndex)
564+
if tokens[linebreakIndex].isSpace, tokens[linebreakIndex] != .space(" ") {
565+
replaceToken(at: linebreakIndex, with: .space(" "))
566+
}
567+
}
568+
}
569+
570+
// --wrapternary
571+
forEach(.operator("?", .infix)) { conditionIndex, _ in
572+
guard options.wrapTernaryOperators != .default,
573+
let expressionStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, before: conditionIndex),
574+
!isInStringLiteralWithWrappingDisabled(at: conditionIndex)
575+
else { return }
576+
577+
// Find the : operator that separates the true and false branches
578+
// of this ternary operator
579+
// - You can have nested ternary operators, so the immediate-next colon
580+
// is not necessarily the colon of _this_ ternary operator.
581+
// - To track nested ternary operators, we maintain a count of
582+
// the unterminated `?` tokens that we've seen.
583+
// - This ternary's colon token is the first colon we find
584+
// where there isn't an unterminated `?`.
585+
var unterimatedTernaryCount = 0
586+
var currentIndex = conditionIndex + 1
587+
var foundColonIndex: Int?
588+
589+
while foundColonIndex == nil,
590+
currentIndex < tokens.count
591+
{
592+
switch tokens[currentIndex] {
593+
case .operator("?", .infix):
594+
unterimatedTernaryCount += 1
595+
case .operator(":", .infix):
596+
if unterimatedTernaryCount == 0 {
597+
foundColonIndex = currentIndex
598+
} else {
599+
unterimatedTernaryCount -= 1
600+
}
601+
default:
602+
break
603+
}
604+
605+
currentIndex += 1
606+
}
607+
608+
guard let colonIndex = foundColonIndex,
609+
let endOfElseExpression = endOfExpression(at: colonIndex, upTo: [])
610+
else { return }
611+
612+
wrapMultilineStatement(
613+
startIndex: expressionStartIndex,
614+
delimiterIndices: [conditionIndex, colonIndex],
615+
endIndex: endOfElseExpression
616+
)
617+
}
618+
525619
var lastIndex = -1
526620
forEachToken(onlyWhereEnabled: false) { i, token in
527621
guard case let .startOfScope(string) = token else {
@@ -800,95 +894,6 @@ extension Formatter {
800894

801895
return true
802896
}
803-
804-
// -- wraptypealiases
805-
forEach(.keyword("typealias")) { typealiasIndex, _ in
806-
guard options.wrapTypealiases == .beforeFirst || options.wrapTypealiases == .afterFirst,
807-
let (equalsIndex, andTokenIndices, lastIdentifierIndex) = parseProtocolCompositionTypealias(at: typealiasIndex)
808-
else { return }
809-
810-
// Decide which indices to wrap at
811-
// - We always wrap at each `&`
812-
// - For `beforeFirst`, we also wrap before the `=`
813-
let wrapIndices: [Int]
814-
switch options.wrapTypealiases {
815-
case .afterFirst:
816-
wrapIndices = andTokenIndices
817-
case .beforeFirst:
818-
wrapIndices = [equalsIndex] + andTokenIndices
819-
case .default, .disabled, .preserve:
820-
return
821-
}
822-
823-
let didWrap = wrapMultilineStatement(
824-
startIndex: typealiasIndex,
825-
delimiterIndices: wrapIndices,
826-
endIndex: lastIdentifierIndex
827-
)
828-
829-
guard didWrap else { return }
830-
831-
// If we're using `afterFirst` and there was unexpectedly a linebreak
832-
// between the `typealias` and the `=`, we need to remove it
833-
let rangeBetweenTypealiasAndEquals = (typealiasIndex + 1) ..< equalsIndex
834-
if options.wrapTypealiases == .afterFirst,
835-
let linebreakIndex = rangeBetweenTypealiasAndEquals.first(where: { tokens[$0].isLinebreak })
836-
{
837-
removeToken(at: linebreakIndex)
838-
if tokens[linebreakIndex].isSpace, tokens[linebreakIndex] != .space(" ") {
839-
replaceToken(at: linebreakIndex, with: .space(" "))
840-
}
841-
}
842-
}
843-
844-
// --wrapternary
845-
forEach(.operator("?", .infix)) { conditionIndex, _ in
846-
guard options.wrapTernaryOperators != .default,
847-
let expressionStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, before: conditionIndex),
848-
!isInStringLiteralWithWrappingDisabled(at: conditionIndex)
849-
else { return }
850-
851-
// Find the : operator that separates the true and false branches
852-
// of this ternary operator
853-
// - You can have nested ternary operators, so the immediate-next colon
854-
// is not necessarily the colon of _this_ ternary operator.
855-
// - To track nested ternary operators, we maintain a count of
856-
// the unterminated `?` tokens that we've seen.
857-
// - This ternary's colon token is the first colon we find
858-
// where there isn't an unterminated `?`.
859-
var unterimatedTernaryCount = 0
860-
var currentIndex = conditionIndex + 1
861-
var foundColonIndex: Int?
862-
863-
while foundColonIndex == nil,
864-
currentIndex < tokens.count
865-
{
866-
switch tokens[currentIndex] {
867-
case .operator("?", .infix):
868-
unterimatedTernaryCount += 1
869-
case .operator(":", .infix):
870-
if unterimatedTernaryCount == 0 {
871-
foundColonIndex = currentIndex
872-
} else {
873-
unterimatedTernaryCount -= 1
874-
}
875-
default:
876-
break
877-
}
878-
879-
currentIndex += 1
880-
}
881-
882-
guard let colonIndex = foundColonIndex,
883-
let endOfElseExpression = endOfExpression(at: colonIndex, upTo: [])
884-
else { return }
885-
886-
wrapMultilineStatement(
887-
startIndex: expressionStartIndex,
888-
delimiterIndices: [conditionIndex, colonIndex],
889-
endIndex: endOfElseExpression
890-
)
891-
}
892897
}
893898

894899
/// Returns the index where the `wrap` rule should add the next linebreak in the line at the selected index.

Sources/Rules/Indent.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -447,12 +447,12 @@ public extension FormatRule {
447447
)) || _nextToken.isMacro || [.keyword("try"), .keyword("await")].contains(_nextToken)) &&
448448
formatter.last(.nonSpaceOrCommentOrLinebreak, before: nextTokenIndex!).map {
449449
$0 != .keyword("return") && !$0.isOperator(ofType: .infix)
450-
} ?? false) || (
450+
} ?? false
451+
) || (
451452
_nextToken == .delimiter(",") && [
452453
"<", "[", "(", "case",
453454
].contains(formatter.currentScope(at: nextTokenIndex!)?.string ?? "")
454-
)
455-
)
455+
))
456456
)
457457

458458
// Determine current indent
@@ -506,7 +506,8 @@ public extension FormatRule {
506506
formatter.isTrailingClosureLabel(at: startIndex)) &&
507507
formatter.last(.nonSpaceOrCommentOrLinebreak, before: startIndex).map {
508508
$0 != .keyword("return") && !$0.isOperator(ofType: .infix)
509-
} ?? false)
509+
} ?? false
510+
)
510511
{
511512
indent += formatter.options.indent
512513
indentStack[indentStack.count - 1] = indent
@@ -883,7 +884,6 @@ extension Formatter {
883884
return false
884885
}
885886

886-
887887
func isWrappedDeclaration(at i: Int) -> Bool {
888888
guard let keywordIndex = indexOfLastSignificantKeyword(at: i, excluding: [
889889
"where", "throws", "rethrows",

Sources/Rules/PreferCountWhere.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import Foundation
1010

1111
public extension FormatRule {
1212
static let preferCountWhere = FormatRule(
13-
help: "Prefer `count(where:)` over `filter(_:).count`.")
14-
{ formatter in
13+
help: "Prefer `count(where:)` over `filter(_:).count`."
14+
) { formatter in
1515
// count(where:) was added in Swift 6.0
1616
guard formatter.options.swiftVersion >= "6.0" else { return }
1717

Sources/Rules/RedundantReturn.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ extension Formatter {
222222
// }
223223
//
224224
if conditionalBranchHasUnsupportedCastOperator(
225-
startOfScopeIndex: branch.startOfBranch)
226-
{
225+
startOfScopeIndex: branch.startOfBranch
226+
) {
227227
return nil
228228
}
229229

Sources/Rules/WrapArguments.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public extension FormatRule {
1919
"tab-width", "max-width", "smart-tabs", "asset-literals", "wrap-ternary"]
2020
) { formatter in
2121
formatter.wrapCollectionsAndArguments(completePartialWrapping: true,
22-
wrapSingleArguments: false)
22+
wrapSingleArguments: true)
2323
} examples: {
2424
"""
2525
**NOTE:** For backwards compatibility with previous versions, if no value is

Sources/Tokenizer.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,8 +1887,7 @@ public func tokenize(_ source: String) -> [Token] {
18871887
convertOpeningChevronToOperator(at: scopeIndex)
18881888
}
18891889
case .delimiter(":") where scopeIndexStack.count > 1 &&
1890-
[.endOfScope("case"), .operator("?", .infix)].contains(tokens[scopeIndexStack[scopeIndexStack.count - 2]]
1891-
):
1890+
[.endOfScope("case"), .operator("?", .infix)].contains(tokens[scopeIndexStack[scopeIndexStack.count - 2]]):
18921891
// Not a generic scope
18931892
convertOpeningChevronToOperator(at: scopeIndex)
18941893
processToken()

Sources/TypeName.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ extension TypeName {
144144

145145
/// Whether this type has a top-level optional suffix (`?` or `!`) applied to it.
146146
var isOptionalType: Bool {
147-
return trailingUnwrapOperatorIndex != nil && !isClosure
147+
trailingUnwrapOperatorIndex != nil && !isClosure
148148
}
149149

150150
private var trailingUnwrapOperatorIndex: Int? {

0 commit comments

Comments
 (0)