diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index d38819ec..2cfac405 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -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) diff --git a/Sources/Rules/BlankLineAfterImports.swift b/Sources/Rules/BlankLineAfterImports.swift index 57f684ac..ba2e18ce 100644 --- a/Sources/Rules/BlankLineAfterImports.swift +++ b/Sources/Rules/BlankLineAfterImports.swift @@ -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 diff --git a/Sources/Rules/DocComments.swift b/Sources/Rules/DocComments.swift index d14fa341..85c88ae3 100644 --- a/Sources/Rules/DocComments.swift +++ b/Sources/Rules/DocComments.swift @@ -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] diff --git a/Sources/Rules/EnumNamespaces.swift b/Sources/Rules/EnumNamespaces.swift index 5c57d87b..1c19956e 100644 --- a/Sources/Rules/EnumNamespaces.swift +++ b/Sources/Rules/EnumNamespaces.swift @@ -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 diff --git a/Sources/Rules/ModifierOrder.swift b/Sources/Rules/ModifierOrder.swift index 168e25ac..ce787af8 100644 --- a/Sources/Rules/ModifierOrder.swift +++ b/Sources/Rules/ModifierOrder.swift @@ -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)" : "") diff --git a/Sources/Rules/RedundantObjc.swift b/Sources/Rules/RedundantObjc.swift index f5158fee..d3401a4f 100644 --- a/Sources/Rules/RedundantObjc.swift +++ b/Sources/Rules/RedundantObjc.swift @@ -43,7 +43,7 @@ public extension FormatRule { nextIndex = endIndex } case let token: - guard token.isModifierKeyword else { + guard formatter.isModifier(at: nextIndex) else { break loop } } diff --git a/Sources/Rules/WrapAttributes.swift b/Sources/Rules/WrapAttributes.swift index 2a7ac007..2c46103d 100644 --- a/Sources/Rules/WrapAttributes.swift +++ b/Sources/Rules/WrapAttributes.swift @@ -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 } diff --git a/Tests/Rules/ModifierOrderTests.swift b/Tests/Rules/ModifierOrderTests.swift index d08e9167..165b4fc8 100644 --- a/Tests/Rules/ModifierOrderTests.swift +++ b/Tests/Rules/ModifierOrderTests.swift @@ -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) + } }