-
-
Notifications
You must be signed in to change notification settings - Fork 767
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
[NullCheckOnMutableProperty] Created rule for when a null-check is performed on a mutable property #4353
[NullCheckOnMutableProperty] Created rule for when a null-check is performed on a mutable property #4353
Conversation
Codecov Report
@@ Coverage Diff @@
## main #4353 +/- ##
============================================
+ Coverage 84.26% 84.35% +0.08%
- Complexity 3263 3273 +10
============================================
Files 473 474 +1
Lines 10336 10405 +69
Branches 1827 1845 +18
============================================
+ Hits 8710 8777 +67
- Misses 667 668 +1
- Partials 959 960 +1
Continue to review full report at Codecov.
|
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.
Could you add these tests too:
class A(private var a: Int?) {
fun foo() {
if (a != null) {
println(2 + 2) // this null check should be fine
}
}
}
class A(private val a: Int?) {
class B(private var a: Int?) {
fun foo() {
if (a != null) {
println(2 + a!!) // should report
}
}
}
fun foo() {
if (a != null) {
println(2 + a!!) // shouldn't report
}
}
}
class A(private var a: Int?) {
inner class B {
fun foo() {
if (a != null) {
println(2 + a!!) // should report
}
}
}
}
I think that you are handling correctly the last two, but just to have tests that ensure it.
* </compliant> | ||
* | ||
* <compliant> |
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 that we only support one "compliant" block, can you merge both?
* </compliant> | |
* | |
* <compliant> | |
* |
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 code wouldn't pass the first test case proposed here, as doing so would mean the rule would have to detect both a null-check for the mutable property and a double-bang operation on the variable. This, essentially, would turn the rule into a sub-rule for UnsafeCallOnNullableType
: anything that NullCheckOnMutableProperty
would pick up would be picked up by that rule as well. It's not a perfectly symmetrical relationship, but I'm not sure that the use case wherein a developer wants to be warned about double-bang operations only if they're being conducted after a null-check is that prevalent.
I'm going to give more examples because I think that it will be more clear: class A(private var a: Int?) {
fun foo() {
if (a != null) {
println(a) // report! This could print an unexpected `null`
}
}
} class A(private var a: Int?) {
fun foo() {
if (a != null) {
println(2 + requireNotNull(a)) // report! This could crash
}
}
} class A(private var a: Int?) {
fun foo() {
if (a != null) {
println("a is not null") // this code is completely fine
}
}
} This code is not related with the usage of Other missing test: class A(private var a: Int?) {
fun foo() {
if (a != null && true) {
println("a is not null") // this code is completely fine
}
}
} |
Gotcha, I'll see what I can do |
…enced within the if-expression block; Moved all relevant inspection code to within a private class to ensure resource cleanup after visiting a Kotlin file
…llCheckOnMutablePropertySpec
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.
🎉🎉🎉 Great job! I really like this rule. This will flag a lot of difficult to spot issues. 👏👏👏
Do you think this is ready to be merged?
} | ||
} | ||
} | ||
""" |
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 would be nice to have another rule to check something like this. But it would be difficult and with lots of false positives.
it("should not report a null-check on a non-mutable class property") { | ||
val code = """ | ||
class A { | ||
private val a: Int? = 5 |
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.
private val a: Int?
get() = 5
Maybe this could be flagged too.
The code should be good to go now. The other two points could be made into other issues/tasks to be tackled later. |
Ok, I'll wait a bit to get the review form other maintainer. I think that this case should be handled: private val a: Int?
get() = 5 But I agree that it could be handled in other PR. Can you do it? Or should we open an issue so we don't forget about it? |
I'd say that it's a different issue, as it wouldn't be "error-prone": the value is going to be non-null, no matter how many times it's checked. Maybe this would be a style issue, e.g. some rule like |
Sorry, I wasn't clear with my example. It was far too simplified. I mean something like this:
|
Why did you closed and removed it? |
My apologies, I thought it was merged - thanks for the heads-up! |
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.
Great work :)
* | ||
* class A(private var a: Int?) { | ||
* fun foo() { | ||
* val a = a |
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.
That's not the best snippet to put in the compliant block. Is doing name shadowing and should not be suggested.
* } | ||
* </compliant> | ||
*/ | ||
|
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.
Extra white line?
) | ||
|
||
override fun visitKtFile(file: KtFile) { | ||
if (bindingContext == BindingContext.EMPTY) return |
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're missing a super.visitKtFile(file)
here
candidateProperties.getOrPut(candidateFqName) { ArrayDeque() }.apply { add(expression) } | ||
} | ||
} | ||
super.visitIfExpression(expression) |
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.
Commenting here but valid for the whole NullCheckVisitor
:
Is there a reason why you're calling super.
methods at arbitrary locations inside the function blocks? Ideally you should always call the super method as first statement and then add your logic.
In this line you're calling super.visitIfExpression(expression)
in the middle of the block.
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 line needs to be in the middle of the block in order to analyze whether the descendant expressions use any candidate properties identified in the if-expression, with the visitIfExpression()
removing any if-statements added to candidateProperties
afterwards. I've added a comment that explains as much in the code.
CodeSmell( | ||
issue, | ||
Entity.from(ifExpression), | ||
"Null-check is being called on a mutable property." |
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.
nit: if you could customize the message with the name of the property would be 👌
Are we good to merge this? |
This is for issue #3353. Any advice on how to improve this one is welcome, as this was a bit of a stabbing-in-the-dark solution for me.