Files
SwiftLint/Source/SwiftLintBuiltInRules/Rules/Lint/DiscardedNotificationCenterObserverRule.swift
Danny Mösch 40bd97038a Support arbitrary configurations in @SwiftSyntaxRule (#5275)
Almost all rules based on SwiftSyntax can be set up now by just adding
`@SwiftSyntaxRule` to the rule struct.
2023-10-16 19:34:43 +02:00

104 lines
4.7 KiB
Swift

import SwiftSyntax
@SwiftSyntaxRule
struct DiscardedNotificationCenterObserverRule: OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "discarded_notification_center_observer",
name: "Discarded Notification Center Observer",
description: "When registering for a notification using a block, the opaque observer that is " +
"returned should be stored so it can be removed later",
kind: .lint,
nonTriggeringExamples: [
Example("let foo = nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil) { }"),
Example("""
let foo = nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { })
"""),
Example("""
func foo() -> Any {
return nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { })
}
"""),
Example("""
var obs: [Any?] = []
obs.append(nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { }))
"""),
Example("""
var obs: [String: Any?] = []
obs["foo"] = nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { })
"""),
Example("""
var obs: [Any?] = []
obs.append(nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { }))
"""),
Example("""
func foo(_ notif: Any) {
obs.append(notif)
}
foo(nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { }))
"""),
Example("""
var obs: [NSObjectProtocol] = [
nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { }),
nc.addObserver(forName: .CKAccountChanged, object: nil, queue: nil, using: { })
]
""")
],
triggeringExamples: [
Example("↓nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil) { }"),
Example("_ = ↓nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil) { }"),
Example("↓nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { })"),
Example("""
@discardableResult func foo() -> Any {
return ↓nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { })
}
""")
]
)
}
private extension DiscardedNotificationCenterObserverRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override func visitPost(_ node: FunctionCallExprSyntax) {
guard
let calledExpression = node.calledExpression.as(MemberAccessExprSyntax.self),
case .identifier("addObserver") = calledExpression.declName.baseName.tokenKind,
case let argumentLabels = node.arguments.map({ $0.label?.text }),
argumentLabels.starts(with: ["forName", "object", "queue"])
else {
return
}
if
let firstParent = node.parent?.as(ReturnStmtSyntax.self),
let secondParent = firstParent.parent?.as(CodeBlockItemSyntax.self),
let thirdParent = secondParent.parent?.as(CodeBlockItemListSyntax.self),
let fourthParent = thirdParent.parent?.as(CodeBlockSyntax.self),
let fifthParent = fourthParent.parent?.as(FunctionDeclSyntax.self),
!fifthParent.attributes.hasDiscardableResultAttribute
{
return // result is returned from a function
} else if node.parent?.is(LabeledExprSyntax.self) == true {
return // result is passed as an argument to a function
} else if node.parent?.is(ArrayElementSyntax.self) == true {
return // result is an array literal element
} else if
let previousToken = node.previousToken(viewMode: .sourceAccurate),
case .equal = previousToken.tokenKind,
previousToken.previousToken(viewMode: .sourceAccurate)?.tokenKind != .wildcard
{
return // result is assigned to something other than the wildcard keyword (`_`)
}
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}
private extension AttributeListSyntax {
var hasDiscardableResultAttribute: Bool {
contains(attributeNamed: "discardableResult")
}
}