Fix issue where modifiersOrder rule confused async effect for async modifier (#2237)

This commit is contained in:
Cal Stephens
2025-09-29 08:24:36 -07:00
parent 1fd4bee35a
commit a3dd82ca0d
8 changed files with 29 additions and 22 deletions
+8 -4
View File
@@ -389,14 +389,14 @@ extension Formatter {
/// Returns true if the token at specified index is a modifier
func isModifier(at index: Int) -> Bool {
guard let token = token(at: index), token.isModifierKeyword else {
guard let token = token(at: index), token._isModifierKeyword else {
return false
}
if token == .keyword("class"),
let nextToken = next(.nonSpaceOrCommentOrLinebreak, after: index)
{
return nextToken.isDeclarationTypeKeyword || nextToken.isModifierKeyword
return nextToken.isDeclarationTypeKeyword || nextToken._isModifierKeyword
}
// Async is only a valid modifier on local let/var declarations.
@@ -2536,7 +2536,7 @@ extension Formatter {
if nextToken.isDeclarationTypeKeyword {
return nextToken.string
}
guard nextToken.isModifierKeyword else {
guard nextToken._isModifierKeyword else {
break
}
nextIndex = i
@@ -3648,7 +3648,11 @@ extension Token {
.contains(keyword)
}
var isModifierKeyword: Bool {
/// Whether or not this token represents a potential modifier keyword.
/// This doesn't necessarily mean that the keyword is a modifier: some modifiers
/// like `class` and `async` are contextual.
/// In rule implementations, prefer using the `Formatter.isModifier(at:)` helper.
var _isModifierKeyword: Bool {
switch self {
case let .keyword(keyword), let .identifier(keyword):
return _FormatRules.allModifiers.contains(keyword)
+1 -1
View File
@@ -21,7 +21,7 @@ public extension FormatRule {
return
}
var keyword: Token = formatter.tokens[nextIndex]
while keyword == .startOfScope("#if") || keyword.isModifierKeyword || keyword.isAttribute,
while keyword == .startOfScope("#if") || formatter.isModifier(at: nextIndex) || keyword.isAttribute,
let index = formatter.index(of: .keyword, after: nextIndex)
{
nextIndex = index
+1 -1
View File
@@ -163,7 +163,7 @@ extension Formatter {
// Check if this token defines a declaration that supports doc comments
var declarationToken = tokens[nextDeclarationIndex]
if declarationToken.isAttribute || declarationToken.isModifierKeyword,
if declarationToken.isAttribute || isModifier(at: nextDeclarationIndex),
let index = index(after: nextDeclarationIndex, where: { $0.isDeclarationTypeKeyword })
{
declarationToken = tokens[index]
+2 -2
View File
@@ -19,11 +19,11 @@ public extension FormatRule {
) { formatter in
formatter.forEachToken(where: { [.keyword("class"), .keyword("struct")].contains($0) }) { i, token in
if token == .keyword("class") {
guard let next = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i),
guard let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i),
// exit if structs only
formatter.options.enumNamespaces != .structsOnly,
// exit if class is a type modifier
!(next.isKeywordOrAttribute || next.isModifierKeyword),
!(formatter.tokens[nextIndex].isKeywordOrAttribute || formatter.isModifier(at: nextIndex)),
// exit for class as protocol conformance
formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .delimiter(":"),
// exit if not closed for extension
+3 -9
View File
@@ -15,13 +15,7 @@ public extension FormatRule {
options: ["modifier-order"]
) { formatter in
formatter.forEach(.keyword) { i, token in
switch token.string {
case "let", "func", "var", "class", "actor", "extension", "init", "enum",
"struct", "typealias", "subscript", "associatedtype", "protocol":
break
default:
return
}
guard token.isDeclarationTypeKeyword else { return }
var modifiers = [String: [Token]]()
var lastModifier: (name: String, tokens: [Token])?
func pushModifier() {
@@ -36,7 +30,7 @@ public extension FormatRule {
lastModifier = nil
lastIndex = previousIndex
break loop
case let token where token.isModifierKeyword:
case let token where formatter.isModifier(at: index):
pushModifier()
lastModifier = (token.string, [Token](formatter.tokens[index ..< lastIndex]))
previousIndex = lastIndex
@@ -45,7 +39,7 @@ public extension FormatRule {
if case let .identifier(param)? = formatter.last(.nonSpaceOrCommentOrLinebreak, before: index),
let openParenIndex = formatter.index(of: .startOfScope("("), before: index),
let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: openParenIndex),
let token = formatter.token(at: index), token.isModifierKeyword
let token = formatter.token(at: index), formatter.isModifier(at: index)
{
pushModifier()
let modifier = token.string + (param == "set" ? "(set)" : "")
+1 -1
View File
@@ -43,7 +43,7 @@ public extension FormatRule {
nextIndex = endIndex
}
case let token:
guard token.isModifierKeyword else {
guard formatter.isModifier(at: nextIndex) else {
break loop
}
}
+2 -4
View File
@@ -17,10 +17,8 @@ public extension FormatRule {
formatter.forEach(.attribute) { i, _ in
// Ignore sequential attributes
guard let endIndex = formatter.endOfAttribute(at: i),
var keywordIndex = formatter.index(
of: .nonSpaceOrCommentOrLinebreak,
after: endIndex, if: { $0.isKeyword || $0.isModifierKeyword }
)
var keywordIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex),
formatter.tokens[keywordIndex].isKeyword || formatter.isModifier(at: keywordIndex)
else {
return
}
+11
View File
@@ -143,4 +143,15 @@ class ModifierOrderTests: XCTestCase {
"""
testFormatting(for: input, rule: .modifierOrder)
}
func testAsyncFunctionBeforeNonisolatedVar() {
let input = """
protocol Test: Actor {
func test() async
nonisolated var test2: String
}
"""
testFormatting(for: input, rule: .modifierOrder)
}
}