-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add NSObjectPreferIsEqualRule #2663
Add NSObjectPreferIsEqualRule #2663
Conversation
Generated by 🚫 Danger |
|
||
static let triggeringExamples: [String] = [ | ||
// NSObject subclass implementing == | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should use the violation marker (↓
) to test where the violation location
CHANGELOG.md
Outdated
* None. | ||
* Add `nsobject_prefer_isequal` rule to warn against implementing `==` on an | ||
`NSObject` subclass as calling `isEqual` (i.e. when using the class from | ||
Objective-C) will will not use the defined `==` method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
requires two trailing spaces as described in CONTRIBUTING.md: https://github.com/realm/SwiftLint/blob/master/CONTRIBUTING.md#tracking-changes
SwiftDeclarationKind(rawValue: kind) == .class | ||
else { return false } | ||
let isDirectNSObjectSubclass = dictionary.inheritedTypes.contains("NSObject") | ||
let isMarkedObjc = dictionary.enclosedSwiftAttributes.contains(where: { $0 == .objc }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: you can use .contains(.objc)
let kind = method.kind.flatMap(SwiftDeclarationKind.init), | ||
let name = method.name, | ||
kind == .functionMethodStatic, | ||
name.hasPrefix("=="), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe check if it's equal to ==(_:_:)
? I'm a bit worried that using just ==
might cause some (very rare) false positives on custom operators.
private func areAllArguments(toMethod method: [String: SourceKitRepresentable], | ||
ofType typeName: String) -> Bool { | ||
return method.enclosedVarParameters.reduce(true) { soFar, param -> Bool in | ||
soFar && (param.typeName == typeName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to say that this might not work for generic types, but @objc
classes can't be generic, so no problem here!
@@ -0,0 +1,68 @@ | |||
import SourceKittenFramework | |||
|
|||
public struct NSObjectPreferIsEqualRule: Rule, OptInRule, ConfigurationProviderRule, AutomaticTestableRule { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could promote this to a default rule, given how easy it's to make a mistake (I've definitely made this mistake several times)
@matthew-healy Thanks so much for this! This is a great first contribution! 💯 I've added a few minor comments, but otherwise, it looks good to me 🚀 |
@marcelofabri Thanks! I think I've addressed all of your comments. 😄 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again ✨
After accidentally mis-implementing
==
on anNSObject
subclass one too many times, I decided to implement this rule. I also filled out the Rule Request info below, to explain why I think it's necessary.Rule Request
When subclassing
NSObject
in Swift, properly implementingEquatable
can be a little tricky. Whereas with most Swift types the correct thing to do is implement==
,NSObject
already exposes an implementation of==
that calls out toisEqual
. Adding a new implementation of==
to anNSObject
subclass can lead to odd situations wherea == b
istrue
, but(a as NSObject) == b
(or, equivalentlya.isEqual(b)
) isfalse
.This rule is triggered whenever an
@objc
class or a directNSObject
subclass implements astatic
==
function with exactly 2 arguments, both of which are of the same type as the class itself.See the triggering & non-triggering examples in
NSObjectPreferIsEqualRuleExamples
.I included a
SeverityConfiguration
, because it seemed reasonable that folk might want to be aware of this violation without fixing it immediately.I'm not entirely sure. I implemented it as an
OptInRule
because it seemed safer, but I also can't think of a particularly good reason why you wouldn't want this to run, given the relative risks of differing notions of equality existing on the same type depending on the context. Happy to either leave as-is or enable by default depending on what others think.