Skip to content
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

Merged
merged 5 commits into from
Mar 4, 2019
Merged

Add NSObjectPreferIsEqualRule #2663

merged 5 commits into from
Mar 4, 2019

Conversation

matthew-healy
Copy link

@matthew-healy matthew-healy commented Mar 1, 2019

After accidentally mis-implementing == on an NSObject 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

  1. Why should this rule be added?

When subclassing NSObject in Swift, properly implementing Equatable 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 to isEqual. Adding a new implementation of == to an NSObject subclass can lead to odd situations where a == b is true, but (a as NSObject) == b (or, equivalently a.isEqual(b)) is false.

This rule is triggered whenever an @objc class or a direct NSObject subclass implements a static == function with exactly 2 arguments, both of which are of the same type as the class itself.

  1. Provide several examples of what would and wouldn't trigger violations.

See the triggering & non-triggering examples in NSObjectPreferIsEqualRuleExamples.

  1. Should the rule be configurable, if so what parameters should be configurable?

I included a SeverityConfiguration, because it seemed reasonable that folk might want to be aware of this violation without fixing it immediately.

  1. Should the rule be opt-in or enabled by default? Why?

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.

@SwiftLintBot
Copy link

SwiftLintBot commented Mar 1, 2019

12 Messages
📖 Linting Aerial with this PR took 2.01s vs 1.92s on master (4% slower)
📖 Linting Alamofire with this PR took 4.49s vs 3.95s on master (13% slower)
📖 Linting Firefox with this PR took 14.31s vs 12.47s on master (14% slower)
📖 Linting Kickstarter with this PR took 23.98s vs 20.17s on master (18% slower)
📖 Linting Moya with this PR took 2.23s vs 1.86s on master (19% slower)
📖 Linting Nimble with this PR took 2.17s vs 1.71s on master (26% slower)
📖 Linting Quick with this PR took 0.68s vs 0.55s on master (23% slower)
📖 Linting Realm with this PR took 3.74s vs 3.37s on master (10% slower)
📖 Linting SourceKitten with this PR took 1.22s vs 1.12s on master (8% slower)
📖 Linting Sourcery with this PR took 4.1s vs 4.01s on master (2% slower)
📖 Linting Swift with this PR took 27.24s vs 26.94s on master (1% slower)
📖 Linting WordPress with this PR took 21.21s vs 20.93s on master (1% slower)

Generated by 🚫 Danger


static let triggeringExamples: [String] = [
// NSObject subclass implementing ==
"""
Copy link
Collaborator

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.
Copy link
Collaborator

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 })
Copy link
Collaborator

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("=="),
Copy link
Collaborator

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)
Copy link
Collaborator

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 {
Copy link
Collaborator

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)

@marcelofabri
Copy link
Collaborator

@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 🚀

@matthew-healy
Copy link
Author

@marcelofabri Thanks! I think I've addressed all of your comments. 😄

Copy link
Collaborator

@marcelofabri marcelofabri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again ✨

@marcelofabri marcelofabri merged commit 6244d98 into realm:master Mar 4, 2019
@matthew-healy matthew-healy deleted the matthew-healy/equatable-nsobject branch March 4, 2019 08:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants