mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
With the binding of configurations to their associated rule types "unknown configuration" errors can be made more specific mentioning also the rule's identifier in the printed message.
99 lines
4.8 KiB
Swift
99 lines
4.8 KiB
Swift
import SwiftSyntax
|
|
|
|
struct DiscardedNotificationCenterObserverRule: SwiftSyntaxRule, ConfigurationProviderRule, 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) { }\n"),
|
|
Example("""
|
|
let foo = nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { })
|
|
"""),
|
|
Example("func foo() -> Any {\n" +
|
|
" return nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { })\n" +
|
|
"}\n"),
|
|
Example("var obs: [Any?] = []\n" +
|
|
"obs.append(nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { }))\n"),
|
|
Example("""
|
|
var obs: [String: Any?] = []
|
|
obs["foo"] = nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { })
|
|
"""),
|
|
Example("var obs: [Any?] = []\n" +
|
|
"obs.append(nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { }))\n"),
|
|
Example("func foo(_ notif: Any) {\n" +
|
|
" obs.append(notif)\n" +
|
|
"}\n" +
|
|
"foo(nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { }))\n"),
|
|
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) { }\n"),
|
|
Example("_ = ↓nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil) { }\n"),
|
|
Example("↓nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { })\n"),
|
|
Example("""
|
|
@discardableResult func foo() -> Any {
|
|
return ↓nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { })
|
|
}
|
|
""")
|
|
]
|
|
)
|
|
|
|
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
Visitor(viewMode: .sourceAccurate)
|
|
}
|
|
}
|
|
|
|
private extension DiscardedNotificationCenterObserverRule {
|
|
final class Visitor: ViolationsSyntaxVisitor {
|
|
override func visitPost(_ node: FunctionCallExprSyntax) {
|
|
guard
|
|
let calledExpression = node.calledExpression.as(MemberAccessExprSyntax.self),
|
|
case .identifier("addObserver") = calledExpression.name.tokenKind,
|
|
case let argumentLabels = node.argumentList.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(TupleExprElementSyntax.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")
|
|
}
|
|
}
|