forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request realm#2663 from matthew-healy/matthew-healy/equata…
…ble-nsobject Add NSObjectPreferIsEqualRule
- Loading branch information
Showing
8 changed files
with
333 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
Source/SwiftLintFramework/Rules/Lint/NSObjectPreferIsEqualRule.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import SourceKittenFramework | ||
|
||
public struct NSObjectPreferIsEqualRule: Rule, ConfigurationProviderRule, AutomaticTestableRule { | ||
public var configuration = SeverityConfiguration(.warning) | ||
|
||
public init() {} | ||
|
||
public static let description = RuleDescription( | ||
identifier: "nsobject_prefer_isequal", | ||
name: "NSObject Prefer isEqual", | ||
description: "NSObject subclasses should implement isEqual instead of ==.", | ||
kind: .lint, | ||
nonTriggeringExamples: NSObjectPreferIsEqualRuleExamples.nonTriggeringExamples, | ||
triggeringExamples: NSObjectPreferIsEqualRuleExamples.triggeringExamples | ||
) | ||
|
||
public func validate(file: File) -> [StyleViolation] { | ||
return objcVisibleClasses(in: file).flatMap { violations(in: file, for: $0) } | ||
} | ||
|
||
// MARK: - Private | ||
|
||
private func objcVisibleClasses(in file: File) -> [[String: SourceKitRepresentable]] { | ||
return file.structure.dictionary.substructure.filter { dictionary in | ||
guard | ||
let kind = dictionary.kind, | ||
SwiftDeclarationKind(rawValue: kind) == .class | ||
else { return false } | ||
let isDirectNSObjectSubclass = dictionary.inheritedTypes.contains("NSObject") | ||
let isMarkedObjc = dictionary.enclosedSwiftAttributes.contains(.objc) | ||
return isDirectNSObjectSubclass || isMarkedObjc | ||
} | ||
} | ||
|
||
private func violations(in file: File, | ||
for dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { | ||
guard let typeName = dictionary.name else { return [] } | ||
return dictionary.substructure.compactMap { subDictionary -> StyleViolation? in | ||
guard | ||
isDoubleEqualsMethod(subDictionary, onType: typeName), | ||
let offset = subDictionary.offset | ||
else { return nil } | ||
return StyleViolation(ruleDescription: type(of: self).description, | ||
severity: configuration.severity, | ||
location: Location(file: file, byteOffset: offset)) | ||
} | ||
} | ||
|
||
private func isDoubleEqualsMethod(_ method: [String: SourceKitRepresentable], | ||
onType typeName: String) -> Bool { | ||
guard | ||
let kind = method.kind.flatMap(SwiftDeclarationKind.init), | ||
let name = method.name, | ||
kind == .functionMethodStatic, | ||
name == "==(_:_:)", | ||
areAllArguments(toMethod: method, ofType: typeName) | ||
else { return false } | ||
return true | ||
} | ||
|
||
private func areAllArguments(toMethod method: [String: SourceKitRepresentable], | ||
ofType typeName: String) -> Bool { | ||
return method.enclosedVarParameters.reduce(true) { soFar, param -> Bool in | ||
soFar && (param.typeName == typeName) | ||
} | ||
} | ||
} |
111 changes: 111 additions & 0 deletions
111
Source/SwiftLintFramework/Rules/Lint/NSObjectPreferIsEqualRuleExamples.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
internal struct NSObjectPreferIsEqualRuleExamples { | ||
static let nonTriggeringExamples: [String] = [ | ||
// NSObject subclass without == | ||
""" | ||
class AClass: NSObject { | ||
} | ||
""", | ||
// @objc class without == | ||
""" | ||
@objc class AClass: SomeNSObjectSubclass { | ||
} | ||
""", | ||
// Class with == which does not subclass NSObject | ||
""" | ||
class AClass: Equatable { | ||
static func ==(lhs: AClass, rhs: AClass) -> Bool { | ||
return true | ||
} | ||
""", | ||
// NSObject subclass implementing isEqual | ||
""" | ||
class AClass: NSObject { | ||
override func isEqual(_ object: Any?) -> Bool { | ||
return true | ||
} | ||
} | ||
""", | ||
// @objc class implementing isEqual | ||
""" | ||
@objc class AClass: SomeNSObjectSubclass { | ||
override func isEqual(_ object: Any?) -> Bool { | ||
return false | ||
} | ||
} | ||
""", | ||
// NSObject subclass with non-static == | ||
""" | ||
class AClass: NSObject { | ||
func ==(lhs: AClass, rhs: AClass) -> Bool { | ||
return true | ||
} | ||
} | ||
""", | ||
// NSObject subclass implementing == with different signature | ||
""" | ||
class AClass: NSObject { | ||
static func ==(lhs: AClass, rhs: BClass) -> Bool { | ||
return true | ||
} | ||
} | ||
""", | ||
// Equatable struct | ||
""" | ||
struct AStruct: Equatable { | ||
static func ==(lhs: AStruct, rhs: AStruct) -> Bool { | ||
return false | ||
} | ||
} | ||
""", | ||
// Equatable enum | ||
""" | ||
enum AnEnum: Equatable { | ||
static func ==(lhs: AnEnum, rhs: AnEnum) -> Bool { | ||
return true | ||
} | ||
} | ||
""" | ||
] | ||
|
||
static let triggeringExamples: [String] = [ | ||
// NSObject subclass implementing == | ||
""" | ||
class AClass: NSObject { | ||
↓static func ==(lhs: AClass, rhs: AClass) -> Bool { | ||
return false | ||
} | ||
} | ||
""", | ||
// @objc class implementing == | ||
""" | ||
@objc class AClass: SomeOtherNSObjectSubclass { | ||
↓static func ==(lhs: AClass, rhs: AClass) -> Bool { | ||
return true | ||
} | ||
} | ||
""", | ||
// Equatable NSObject subclass implementing == | ||
""" | ||
class AClass: NSObject, Equatable { | ||
↓static func ==(lhs: AClass, rhs: AClass) -> Bool { | ||
return false | ||
} | ||
} | ||
""", | ||
// NSObject subclass overriding isEqual and implementing == | ||
""" | ||
class AClass: NSObject { | ||
override func isEqual(_ object: Any?) -> Bool { | ||
guard let other = object as? AClass else { | ||
return false | ||
} | ||
return true | ||
} | ||
↓static func ==(lhs: AClass, rhs: AClass) -> Bool { | ||
return false | ||
} | ||
} | ||
""" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.