Fix some `unhandled_throwing_task` false positives (#5001)
Fixes some of the cases reported in https://github.com/realm/SwiftLint/issues/4987
This commit is contained in:
parent
15a18fd4e8
commit
1b1b19a902
|
@ -19,7 +19,12 @@
|
||||||
|
|
||||||
* Make `unhandled_throwing_task` opt-in instead of enabled by default. The rule
|
* Make `unhandled_throwing_task` opt-in instead of enabled by default. The rule
|
||||||
is still prone to false positives at this point, so this makes enabling the
|
is still prone to false positives at this point, so this makes enabling the
|
||||||
rule a conscious decision by end-users instead of enabled by default.
|
rule a conscious decision by end-users.
|
||||||
|
[JP Simard](https://github.com/jpsim)
|
||||||
|
[#4987](https://github.com/realm/SwiftLint/issues/4987)
|
||||||
|
|
||||||
|
* Fix `unhandled_throwing_task` false positives when the `Task` is returned or
|
||||||
|
where the throwing code is handled in a `Result` initializer.
|
||||||
[JP Simard](https://github.com/jpsim)
|
[JP Simard](https://github.com/jpsim)
|
||||||
[#4987](https://github.com/realm/SwiftLint/issues/4987)
|
[#4987](https://github.com/realm/SwiftLint/issues/4987)
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,28 @@ struct UnhandledThrowingTaskRule: ConfigurationProviderRule, SwiftSyntaxRule, Op
|
||||||
let result = await Task {
|
let result = await Task {
|
||||||
throw CancellationError()
|
throw CancellationError()
|
||||||
}.result
|
}.result
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
func makeTask() -> Task<String, Error> {
|
||||||
|
return Task {
|
||||||
|
try await someThrowingFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
func makeTask() -> Task<String, Error> {
|
||||||
|
// Implicit return
|
||||||
|
Task {
|
||||||
|
try await someThrowingFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
Task {
|
||||||
|
return Result {
|
||||||
|
try someThrowingFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
""")
|
""")
|
||||||
],
|
],
|
||||||
triggeringExamples: [
|
triggeringExamples: [
|
||||||
|
@ -151,6 +173,13 @@ struct UnhandledThrowingTaskRule: ConfigurationProviderRule, SwiftSyntaxRule, Op
|
||||||
throw BarError()
|
throw BarError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
func doTask() {
|
||||||
|
↓Task {
|
||||||
|
try await someThrowingFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
""")
|
""")
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -174,7 +203,7 @@ private extension FunctionCallExprSyntax {
|
||||||
var hasViolation: Bool {
|
var hasViolation: Bool {
|
||||||
isTaskWithImplicitErrorType &&
|
isTaskWithImplicitErrorType &&
|
||||||
doesThrow &&
|
doesThrow &&
|
||||||
!(isAssigned || isValueOrResultAccessed)
|
!(isAssigned || isValueOrResultAccessed || isReturnValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isTaskWithImplicitErrorType: Bool {
|
var isTaskWithImplicitErrorType: Bool {
|
||||||
|
@ -260,6 +289,22 @@ private final class ThrowsVisitor: SyntaxVisitor {
|
||||||
return .skipChildren
|
return .skipChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
|
||||||
|
// No need to continue traversing if we already throw.
|
||||||
|
if doesThrow {
|
||||||
|
return .skipChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result initializers with trailing closures handle thrown errors.
|
||||||
|
if let typeIdentifier = node.calledExpression.as(IdentifierExprSyntax.self),
|
||||||
|
typeIdentifier.identifier.text == "Result",
|
||||||
|
node.trailingClosure != nil {
|
||||||
|
return .skipChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
return .visitChildren
|
||||||
|
}
|
||||||
|
|
||||||
override func visitPost(_ node: TryExprSyntax) {
|
override func visitPost(_ node: TryExprSyntax) {
|
||||||
if node.questionOrExclamationMark == nil {
|
if node.questionOrExclamationMark == nil {
|
||||||
doesThrow = true
|
doesThrow = true
|
||||||
|
@ -270,3 +315,30 @@ private final class ThrowsVisitor: SyntaxVisitor {
|
||||||
doesThrow = true
|
doesThrow = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension SyntaxProtocol {
|
||||||
|
var isExplicitReturnValue: Bool {
|
||||||
|
parent?.is(ReturnStmtSyntax.self) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
var isImplicitReturnValue: Bool {
|
||||||
|
// 4th parent: FunctionDecl
|
||||||
|
// 3rd parent: | CodeBlock
|
||||||
|
// 2nd parent: | CodeBlockItemList
|
||||||
|
// 1st parent: | CodeBlockItem
|
||||||
|
// Current node: | FunctionDeclSyntax
|
||||||
|
guard
|
||||||
|
let parentFunctionDecl = parent?.parent?.parent?.parent?.as(FunctionDeclSyntax.self),
|
||||||
|
parentFunctionDecl.body?.statements.count == 1,
|
||||||
|
parentFunctionDecl.signature.output != nil
|
||||||
|
else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var isReturnValue: Bool {
|
||||||
|
isExplicitReturnValue || isImplicitReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue