Ignore override declarations in async_without_await rule (#6417)

Co-authored-by: Danny Mösch <danny.moesch@icloud.com>
This commit is contained in:
Copilot
2026-01-07 20:26:30 +01:00
committed by GitHub
parent cb578f4629
commit 7a9186a39b
3 changed files with 60 additions and 23 deletions
+4
View File
@@ -16,6 +16,10 @@
### Bug Fixes
* Ignore `override` functions in `async_without_await` rule.
[SimplyDanny](https://github.com/SimplyDanny)
[#6416](https://github.com/realm/SwiftLint/issues/6416)
* Fix false positive in `unneeded_escaping` rule when an escaping closure is used in
a nested closure preceded by another closure.
[SimplyDanny](https://github.com/SimplyDanny)
@@ -26,15 +26,10 @@ private extension AsyncWithoutAwaitRule {
private var pendingAsync: TokenSyntax?
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
guard node.body != nil else {
return .visitChildren
if node.body != nil {
let asyncToken = node.needsToKeepAsync ? nil : node.signature.effectSpecifiers?.asyncSpecifier
functionScopes.push(.init(asyncToken: asyncToken))
}
// @concurrent functions require the async keyword even without await calls
let asyncToken = node.attributes.contains(attributeNamed: "concurrent")
? nil : node.signature.effectSpecifiers?.asyncSpecifier
functionScopes.push(.init(asyncToken: asyncToken))
return .visitChildren
}
@@ -45,7 +40,7 @@ private extension AsyncWithoutAwaitRule {
}
override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
// @concurrent closures require the async keyword even without await calls
// @concurrent closures require the async keyword even without await calls,
let asyncToken = (node.signature?.attributes.contains(attributeNamed: "concurrent") ?? false)
? nil : pendingAsync
functionScopes.push(.init(asyncToken: asyncToken))
@@ -62,13 +57,10 @@ private extension AsyncWithoutAwaitRule {
}
override func visit(_ node: AccessorDeclSyntax) -> SyntaxVisitorContinueKind {
guard node.body != nil else {
return .visitChildren
if node.body != nil {
let asyncToken = node.needsToKeepAsync ? nil : node.effectSpecifiers?.asyncSpecifier
functionScopes.push(.init(asyncToken: asyncToken))
}
let asyncToken = node.effectSpecifiers?.asyncSpecifier
functionScopes.push(.init(asyncToken: asyncToken))
return .visitChildren
}
@@ -79,15 +71,10 @@ private extension AsyncWithoutAwaitRule {
}
override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind {
guard node.body != nil else {
return .visitChildren
if node.body != nil {
let asyncToken = node.needsToKeepAsync ? nil : node.signature.effectSpecifiers?.asyncSpecifier
functionScopes.push(.init(asyncToken: asyncToken))
}
// @concurrent can be applied to initializers
let asyncToken = node.attributes.contains(attributeNamed: "concurrent")
? nil : node.signature.effectSpecifiers?.asyncSpecifier
functionScopes.push(.init(asyncToken: asyncToken))
return .visitChildren
}
@@ -163,3 +150,21 @@ private extension TypeSyntax {
return nil
}
}
private extension WithModifiersSyntax where Self: WithAttributesSyntax {
var needsToKeepAsync: Bool {
attributes.contains(attributeNamed: "concurrent") || modifiers.contains(keyword: .override)
}
}
private extension SyntaxProtocol {
var needsToKeepAsync: Bool {
if let variableDecl = `as`(VariableDeclSyntax.self) {
return variableDecl.needsToKeepAsync
}
if let subscriptDecl = `as`(SubscriptDeclSyntax.self) {
return subscriptDecl.needsToKeepAsync
}
return parent?.needsToKeepAsync ?? false
}
}
@@ -188,6 +188,34 @@ internal struct AsyncWithoutAwaitRuleExamples {
let c: () async -> Int = { @concurrent in 1 }
}
"""),
Example("""
class Parent {
func test() async { await foo() }
}
class Child: Parent {
override func test() async { print("Child") }
}
"""),
Example("""
class Parent {
var prop: Int {
get async { await fetchValue() }
}
}
class Child: Parent {
override var prop: Int {
get async { return 2 }
}
}
"""),
Example("""
class Base {
init() async { await setup() }
}
class Derived: Base {
override init() async { print("Derived") }
}
"""),
]
static let triggeringExamples = [