-
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
Added ExplicitSelfRule #829
Conversation
Not sure what to do with those failing tests? - looks like its swiftlint integration tests? |
I’m away from a computer right now, so I can’t be sure. But usually that means that the swiftlint code is generating errors or warnings while being linted by swiftlint itself. Try linting your local copy to see if this is the case.
|
Ahh you might be right - that trailing whitespace always gets me! |
Current coverage is 86.59% (diff: 100%)@@ master #829 diff @@
==========================================
Files 112 113 +1
Lines 4953 5162 +209
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 4260 4470 +210
+ Misses 693 692 -1
Partials 0 0
|
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.
Awesome! I've been looking forward to this rule 👍
|
||
//get the members body (functions and calculated properties will have this data) | ||
guard | ||
let bodyLocation = (dictionary["key.bodyoffset"] as? Int64).flatMap({ Int($0) }), |
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 probably want as! Int64?
so that this doesn't ignore wrong types.
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.
🤔 as?
seems to be the convention throughout - I think it would be preferential to a crash anyway
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.
No, it would be preferable to crash over a silent bug. Here you just want to know if the value is not present.
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'm still inclined to leave it this way, every other rule is using code like this right now
//get the members body (functions and calculated properties will have this data) | ||
guard | ||
let bodyLocation = (dictionary["key.bodyoffset"] as? Int64).flatMap({ Int($0) }), | ||
let bodyLength = (dictionary["key.bodylength"] as? Int64).flatMap({ Int($0) }) |
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.
Can you use flatMap(Int.init)
here?
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.
Nope, It cant disambiguate (they are all .init(_ value: Foo)
)
"class Good2 { let value: Int = 42; func function() -> Int { return self.value } }", | ||
"class Good3 { let value: Int; init() { self.value = 42 } }", | ||
], | ||
triggeringExamples: [ |
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.
Awesome 👍
name: "Explict Self", | ||
description: "Require explicit self for instance members", | ||
nonTriggeringExamples: [ | ||
"class Good1 { let value: Int = 42; var property: Int { return self.value } }", |
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 would add a few more cases here to verify that global variables, members of other types, etc. don't trigger violations.
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.
And maybe same for accessing variables in a scope other than a type (like global values or local variables in a global function scope.
Also static
functions! (Sorry, just thinking of more as I go hehe)
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.
Keep them coming!! 👍
triggeringExamples: [ | ||
"class Bad1 { let value: Int = 42; var property: Int { return value } }", | ||
"class Bad2 { let value: Int = 42; func function() -> Int { return value } }", | ||
"class Bad3 { let value: Int; init() { value = 42 } }", |
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 can think of a few other cases:
- Subclassing: accessing a member from the parent class.
struct
andenum
(because why not)extension
: verify that accessing a member from a function in an extension triggers this too.
|
||
func suffix(matches items: [[Generator.Element]]) -> Bool { | ||
for test in items | ||
where self.suffix(matches: test) { return true } |
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.
This can be simpler using Array.contains
, I think simply return items.contains(self.suffix)
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.
it calls the extension function (not the built in .suffix
) above, this one is just a convenience to perform multiple checks at once
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 know. You can use the appropriate function as the parameter to contas
then. My point is that you don't need the loop, this is a common construct: return true
if any element meets the criterion, which is what Array.contains
does.
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.
perhaps I'm misunderstanding what you mean? this function takes an Array<Array<Generator.Element>>
and neither this function or the one it calls is equivalent to .contains
- they are both checking the tail of the array for a set of elements in a specific order. can you perhaps show me a snippet of what you mean?
Here's 2 interesting examples we might see that I'm not sure are covered here: struct Foo {
var a = 1
}
extension Foo {
func bar() {
print(a)
}
}
Foo().bar() and struct Foo {
var something = 1
}
extension Foo {
func bar(something: Int) {
print(something)
}
}
Foo().bar(something: 1) |
ah nice @keith ! @NachoSoto pointed out the first in his comments but the second will be an interesting one 😄 I'll dive into the suggestions this weekend at some point. |
So I've significantly improved the test coverage thanks to your suggestions guys which has made the code much more robust 👌 The covered cases are (all tested with class and struct (except subclasses obviously)):
as well as some edge cases:
class Edge1 {
let value: Int = 42
func function(value: Int) -> Int { return value /* refers to parameter */ }
}
class Edge2 {
func value() -> Int { return 42 }
func function(value: Int) -> Int { return value /* refers to parameter */ }
} Thanks @NachoSoto and @keith for the suggestions 👍 |
+ "class Bad18_B: Bad18_A { func function() -> Int { return value() } }", | ||
] | ||
|
||
let nonTriggeringExamples = ["class", "struct"].flatMap(nonTriggering) |
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.
👍
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.
Fantastic 👌
I just ran this on our codebase and found a few false positives. Here's one: struct Foo {
var bar: Int
func baz() {
let bar = 123
_ = bar
}
} |
Here's another interesting one: struct Foo {
func bar() {
self.foo.bar()
}
} |
And another: struct Foo {
func baz() {
let foo = 1
foo.something()
}
}
struct Qux {
func something() {}
} |
I think some of these require more semantic understanding that what we have in SwiftLint. So maybe it isn't really feasible, but I can provide a few more examples if needed. |
@keith hmm I can investigate that first one, but the others don't appear to be valid code? I'm not sure it's within the scope of a rule to lint code that otherwise doesn't compile? |
@keith Updated to handle local scope shadowing |
Sorry, I didn't provide full enough examples here, but each of these cases was pulled from our real code and just made smaller for examples. |
Ah right, well if you can turn examples 2 & 3 above into full working snippets I'm happy to take a look 😄 |
public init() { } | ||
|
||
public static let description = RuleDescription( | ||
identifier: "explicitSelf", |
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 rule identifier should be snake case.
public static let description = RuleDescription( | ||
identifier: "explicitSelf", | ||
name: "Explict Self", | ||
description: "Require explicit self for instance members", |
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.
The wording seems weird. How about "Explicit self
for instance members and functions is required".
Given the number of false positives this triggers, I'm glad this is implemented as an opt-in rule. @IanKeen would you mind rebasing this against On a side note, this type of rule would benefit from being implemented in a new mode of SwiftLint that I've been considering: one that operates after the language Parse phase, actually after the Semantic Analysis phase, which would let SwiftLint know the difference between instance struct Foo {
var bar: Int
func baz() {
let bar = 123
_ = bar
}
} This type of mode would require that the module being analyzed 1) already be built by the compiler and 2) that SwiftLint would somehow know all the compiler flags needed to build the module. But if we can find a good user experience for this, it would enable much more powerful rules, from multi-file aware linting (e.g. #836) to static analysis. This is much more straightforward for using with the Swift Package Manger since it generates a build manifest which includes all the compiler arguments ( The way jazzy does this is by straight up cleaning and building the Xcode project, parsing the I hope I can sit down at one point and file an issue tracking exactly how building such a mode for SwiftLint could be built. Until then, I'm sharing these loose thoughts here to spur ideas among SwiftLint contributors and users. |
9870802
to
f5ddc3a
Compare
@jpsim weird, should be good now |
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'd like to see someone that follows this style in their codebase run it against this rule before signing off... Maybe @NachoSoto?
|
||
extension SyntaxToken { | ||
func name(file: File) -> String { | ||
return file.contents.substring(self.offset, length: self.length) |
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.
Even though that's the opposite of what this rule is enforcing, this project prefers omitting self.
wherever possible. Could you please adapt your code to be consistent with this?
.componentsSeparatedByString(",") | ||
|
||
var signature = "" | ||
for param in params { |
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.
this could be refactored as let signature = reduce("") { ... }
import Foundation | ||
import SourceKittenFramework | ||
|
||
func nonTriggering(type: String) -> [String] { |
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.
all the implicitly internal
members in this file that aren't needed in other files should be marked as private
or fileprivate
.
@jpsim I'll test this on Monday! |
@NachoSoto friendly reminder to test this when you get a chance 👍 |
@IanKeen sorry for landing more changes recently and causing merge conflicts with this. Can you please rebase this so we can confirm it builds and passes tests on Linux? Can anyone think of a popular OSS project that already implicitly adheres to this rule (in spirit at least) so that we can run this against a real codebase to make sure it works as intended, without too many false positives? |
If you still need libraries to test this against, CoreStore observes this rule. |
Any status on this @IanKeen ? 😄 |
Can we expect this to be merged soon? |
ugh, very sorry I've had 0 time to jump back on this... I would love it if you wanna take this one @sammy-SC ... work + personal duties has left me no time for anything fun 😢 |
@IanKeen if you don't mind, could you give me push access to this branch? Otherwise I would have to open new PR. |
@sammy-SC no idea how to do that, just added you as a collaborator on the repo 👍 |
Generated by 🚫 danger |
@IanKeen Thanks, that's exactly what I had in mind. I tried running this rule against https://github.com/JohnEstropia/CoreStore, it produces lots of false negatives, I am looking into it. |
FWIW I've ran it on our codebase of ~70K LOC where we require It seems like a rule that needs this much context should really be run outside of SwiftLint against the AST, since otherwise it will never not produce significant numbers of false positives. Just my 2 cents. |
I'm closing this for now due to lack of activity and the high number of false positives. Feel free to open another pull request if the issues are fixed. |
Requires the explicit use of
self.
when accessing instance membersIssue: #321