Skip to content

Commit ae96a86

Browse files
authored
Update singlePropertyPerLine to preserve async let declarations (#2210)
1 parent 47274e9 commit ae96a86

File tree

6 files changed

+74
-10
lines changed

6 files changed

+74
-10
lines changed

Rules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1590,7 +1590,7 @@ Option | Description
15901590
```
15911591

15921592
**NOTE:** If the `--modifier-order` option isn't set, the default order will be:
1593-
`override`, `private`, `fileprivate`, `internal`, `package`, `public`, `open`, `private(set)`, `fileprivate(set)`, `internal(set)`, `package(set)`, `public(set)`, `open(set)`, `final`, `dynamic`, `optional`, `required`, `convenience`, `indirect`, `isolated`, `nonisolated`, `nonisolated(unsafe)`, `lazy`, `weak`, `unowned`, `unowned(safe)`, `unowned(unsafe)`, `static`, `class`, `borrowing`, `consuming`, `mutating`, `nonmutating`, `prefix`, `infix`, `postfix`
1593+
`override`, `private`, `fileprivate`, `internal`, `package`, `public`, `open`, `private(set)`, `fileprivate(set)`, `internal(set)`, `package(set)`, `public(set)`, `open(set)`, `final`, `dynamic`, `optional`, `required`, `convenience`, `indirect`, `isolated`, `nonisolated`, `nonisolated(unsafe)`, `lazy`, `weak`, `unowned`, `unowned(safe)`, `unowned(unsafe)`, `static`, `class`, `borrowing`, `consuming`, `mutating`, `nonmutating`, `prefix`, `infix`, `postfix`, `async`
15941594

15951595
</details>
15961596
<br/>

Sources/ParsingHelpers.swift

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,6 +1949,7 @@ extension Formatter {
19491949
// Get declaration keyword
19501950
var searchIndex = declarationKeywordIndex
19511951
let declarationKeyword = declarationType(at: declarationKeywordIndex) ?? "#if"
1952+
var endOfDeclaration: Int?
19521953
switch tokens[declarationKeywordIndex] {
19531954
case .startOfScope("#if"):
19541955
// For conditional compilation blocks, the `declarationKeyword` _is_ the `startOfScope`
@@ -1976,21 +1977,35 @@ extension Formatter {
19761977
case .keyword("let"), .keyword("var"):
19771978
if let propertyDeclaration = parsePropertyDeclaration(atIntroducerIndex: declarationKeywordIndex) {
19781979
searchIndex = propertyDeclaration.range.upperBound
1980+
endOfDeclaration = propertyDeclaration.range.upperBound
1981+
}
1982+
case .keyword("func"), .keyword("subscript"), .keyword("init"):
1983+
if let functionDeclaration = parseFunctionDeclaration(keywordIndex: declarationKeywordIndex) {
1984+
searchIndex = functionDeclaration.range.upperBound
1985+
endOfDeclaration = functionDeclaration.range.upperBound
19791986
}
19801987
default:
19811988
break
19821989
}
19831990

1984-
// Search for the next declaration so we know where this declaration ends.
1985-
let nextDeclarationKeywordIndex = index(after: searchIndex, where: {
1986-
$0.isDeclarationTypeKeyword || $0 == .startOfScope("#if")
1987-
})
1991+
// Search for the next declaration so we know where this declaration ends
1992+
// (the token before the first token of the following declaration).
19881993

1989-
// Search backward from the next declaration keyword to find where declaration begins.
1990-
var endOfDeclaration = nextDeclarationKeywordIndex.flatMap {
1991-
index(before: startOfModifiers(at: $0, includingAttributes: true), where: {
1992-
!$0.isSpaceOrCommentOrLinebreak
1993-
}).map { endOfLine(at: $0) }
1994+
if let nextDeclarationKeywordIndex = index(after: searchIndex, where: {
1995+
$0.isDeclarationTypeKeyword || $0 == .startOfScope("#if")
1996+
}),
1997+
let lastIndexBeforeNextDeclaration = index(
1998+
before: startOfModifiers(at: nextDeclarationKeywordIndex, includingAttributes: true),
1999+
where: { !$0.isSpaceOrCommentOrLinebreak }
2000+
).map({ endOfLine(at: $0) })
2001+
{
2002+
// If we have an existing `endOfDeclaration` index from a parsing implementation like
2003+
// `parsePropertyDeclaration` or `parseFunctionDeclaration`, prefer that index.
2004+
if let existingEndOfDeclarationValue = endOfDeclaration {
2005+
endOfDeclaration = max(existingEndOfDeclarationValue, lastIndexBeforeNextDeclaration)
2006+
} else {
2007+
endOfDeclaration = lastIndexBeforeNextDeclaration
2008+
}
19942009
}
19952010

19962011
// Prefer keeping linebreaks at the end of a declaration's tokens,
@@ -3493,6 +3508,7 @@ extension _FormatRules {
34933508
["static", "class"],
34943509
mutatingModifiers,
34953510
["prefix", "infix", "postfix"],
3511+
["async"],
34963512
]
34973513

34983514
/// Global swift functions

Sources/Rules/SinglePropertyPerLine.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ public extension FormatRule {
2222
return
2323
}
2424

25+
// Skip `async let` declarations
26+
if formatter.modifiersForDeclaration(at: i, contains: "async") {
27+
return
28+
}
29+
2530
// If this property is within a parenthesis scope, this is probably
2631
// within a switch case like `case (let foo, bar):`
2732
if let startOfScope = formatter.startOfScope(at: i),

Tests/ParsingHelpersTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,7 @@ class ParsingHelpersTests: XCTestCase {
824824
"static", "class",
825825
"borrowing", "consuming", "mutating", "nonmutating",
826826
"prefix", "infix", "postfix",
827+
"async",
827828
])
828829
}
829830

@@ -848,6 +849,7 @@ class ParsingHelpersTests: XCTestCase {
848849
"convenience",
849850
"weak", "unowned", "unowned(safe)", "unowned(unsafe)",
850851
"prefix", "infix", "postfix",
852+
"async",
851853
])
852854
}
853855

Tests/Rules/OrganizeDeclarationsTests.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4249,4 +4249,37 @@ class OrganizeDeclarationsTests: XCTestCase {
42494249
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
42504250
)
42514251
}
4252+
4253+
func testOrganizesProtocolWithAsync() {
4254+
// Async variables are not allowed in protocols
4255+
let input = """
4256+
protocol Foo {
4257+
func foo() async
4258+
var bar: Bar { get }
4259+
4260+
func baaz()
4261+
async
4262+
var quux: Quux { get }
4263+
}
4264+
"""
4265+
4266+
let output = """
4267+
protocol Foo {
4268+
var bar: Bar { get }
4269+
4270+
var quux: Quux { get }
4271+
4272+
func foo() async
4273+
func baaz()
4274+
async
4275+
}
4276+
"""
4277+
4278+
testFormatting(
4279+
for: input, output,
4280+
rule: .organizeDeclarations,
4281+
options: FormatOptions(organizeTypes: ["protocol"]),
4282+
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
4283+
)
4284+
}
42524285
}

Tests/Rules/SinglePropertyPerLineTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,4 +1112,12 @@ class SinglePropertyPerLineTests: XCTestCase {
11121112
"""
11131113
testFormatting(for: input, rule: .singlePropertyPerLine)
11141114
}
1115+
1116+
func testAsyncLetPreserved() {
1117+
let input = """
1118+
async let (one, two) = (performOne(), performTwo())
1119+
let (oneResult, twoResult) = await (one, two)
1120+
"""
1121+
testFormatting(for: input, rule: .singlePropertyPerLine)
1122+
}
11151123
}

0 commit comments

Comments
 (0)