mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
b83e0991b9
The MIT license doesn't require that all files be prepended with this licensing or copyright information. Realm confirmed that they're ok with this change. This will enable some companies to contribute to SwiftLint and the date & authorship information will remain accessible via git source control.
93 lines
3.3 KiB
Swift
93 lines
3.3 KiB
Swift
import Foundation
|
|
import SourceKittenFramework
|
|
|
|
public struct ClassDelegateProtocolRule: ASTRule, ConfigurationProviderRule {
|
|
public var configuration = SeverityConfiguration(.warning)
|
|
|
|
public init() {}
|
|
|
|
public static let description = RuleDescription(
|
|
identifier: "class_delegate_protocol",
|
|
name: "Class Delegate Protocol",
|
|
description: "Delegate protocols should be class-only so they can be weakly referenced.",
|
|
kind: .lint,
|
|
nonTriggeringExamples: [
|
|
"protocol FooDelegate: class {}\n",
|
|
"protocol FooDelegate: class, BarDelegate {}\n",
|
|
"protocol Foo {}\n",
|
|
"class FooDelegate {}\n",
|
|
"@objc protocol FooDelegate {}\n",
|
|
"@objc(MyFooDelegate)\n protocol FooDelegate {}\n",
|
|
"protocol FooDelegate: BarDelegate {}\n",
|
|
"protocol FooDelegate: AnyObject {}\n",
|
|
"protocol FooDelegate: NSObjectProtocol {}\n"
|
|
],
|
|
triggeringExamples: [
|
|
"↓protocol FooDelegate {}\n",
|
|
"↓protocol FooDelegate: Bar {}\n"
|
|
]
|
|
)
|
|
|
|
private let referenceTypeProtocols: Set = ["AnyObject", "NSObjectProtocol", "class"]
|
|
|
|
public func validate(file: File, kind: SwiftDeclarationKind,
|
|
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
|
|
guard kind == .protocol else {
|
|
return []
|
|
}
|
|
|
|
// Check if name contains "Delegate"
|
|
guard let name = dictionary.name, isDelegateProtocol(name) else {
|
|
return []
|
|
}
|
|
|
|
// Check if @objc
|
|
let objcAttributes: Set<SwiftDeclarationAttributeKind> = [.objc, .objcName]
|
|
let isObjc = !objcAttributes.isDisjoint(with: dictionary.enclosedSwiftAttributes)
|
|
guard !isObjc else {
|
|
return []
|
|
}
|
|
|
|
// Check if inherits from another Delegate protocol
|
|
guard dictionary.inheritedTypes.filter(isDelegateProtocol).isEmpty else {
|
|
return []
|
|
}
|
|
|
|
// Check if inherits from a known reference type protocol
|
|
guard dictionary.inheritedTypes.filter(isReferenceTypeProtocol).isEmpty else {
|
|
return []
|
|
}
|
|
|
|
// Check if : class
|
|
guard let offset = dictionary.offset,
|
|
let nameOffset = dictionary.nameOffset,
|
|
let nameLength = dictionary.nameLength,
|
|
let bodyOffset = dictionary.bodyOffset,
|
|
case let contents = file.contents.bridge(),
|
|
case let start = nameOffset + nameLength,
|
|
let range = contents.byteRangeToNSRange(start: start, length: bodyOffset - start),
|
|
!isClassProtocol(file: file, range: range) else {
|
|
return []
|
|
}
|
|
|
|
return [
|
|
StyleViolation(ruleDescription: type(of: self).description,
|
|
severity: configuration.severity,
|
|
location: Location(file: file, byteOffset: offset))
|
|
]
|
|
}
|
|
|
|
private func isClassProtocol(file: File, range: NSRange) -> Bool {
|
|
return !file.match(pattern: "\\bclass\\b", with: [.keyword], range: range).isEmpty
|
|
}
|
|
|
|
private func isDelegateProtocol(_ name: String) -> Bool {
|
|
return name.hasSuffix("Delegate")
|
|
}
|
|
|
|
private func isReferenceTypeProtocol(_ name: String) -> Bool {
|
|
return referenceTypeProtocols.contains(name)
|
|
}
|
|
|
|
}
|