-
-
Notifications
You must be signed in to change notification settings - Fork 797
GuardCluase also matches if-with-body that contains a return #2671
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
GuardCluase also matches if-with-body that contains a return #2671
Conversation
…ns a return as guard-clasue as well
context("a file with an if condition guard clause with body and 2 returns") { | ||
val code = """ | ||
fun test(x: Int): Int { | ||
if (x < 4) { |
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 want this to count as a GuardClause.
We have many of these in our code.
In fact, many consider it a bad practice to have an if
with no body, so they'll never enjoy the benefit of the GuardClause
feature.
Codecov Report
@@ Coverage Diff @@
## master #2671 +/- ##
============================================
- Coverage 79.98% 79.50% -0.49%
+ Complexity 2307 2293 -14
============================================
Files 380 380
Lines 6781 6782 +1
Branches 1229 1229
============================================
- Hits 5424 5392 -32
- Misses 740 774 +34
+ Partials 617 616 -1
Continue to review full report at Codecov.
|
@@ -24,8 +24,8 @@ fun KtNamedFunction.yieldStatementsSkippingGuardClauses(): Sequence<KtExpression | |||
|
|||
fun KtExpression.isGuardClause(): Boolean { | |||
|
|||
fun KtReturnExpression.isIfConditionGuardClause(): Boolean { | |||
val ifExpr = this.parent?.parent as? KtIfExpression | |||
fun KtReturnExpression.isIfConditionGuardClause(ancestorExpression: KtExpression): Boolean { |
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.
fun KtReturnExpression.isIfConditionGuardClause(ancestorExpression: KtExpression): Boolean { | |
fun isIfConditionGuardClause(ancestorExpression: KtExpression): Boolean { |
This can probably be refactored to not be an extension function?
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 actually like it that way,
and anyway I don't feel comfortable to make that change to someone else's code 🙂
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.
+1
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.
@cortinico is right here. As you are not using the receiver inside the nested method is it unused.
I would propose an additional change which checks if the return statement is actually the last statement inside the if:
fun KtExpression.isGuardClause(): Boolean {
fun isIfConditionGuardClause(returnExpr: KtReturnExpression): Boolean {
val ifExpr = this as? KtIfExpression
return ifExpr != null &&
ifExpr.`else` == null &&
returnExpr === ifExpr.then?.lastBlockStatementOrThis()
}
fun KtReturnExpression.isElvisOperatorGuardClause(): Boolean {
val elvisExpr = this.parent as? KtBinaryExpression
return elvisExpr != null && elvisExpr.operationToken == KtTokens.ELVIS
}
val returnExpr = this.findDescendantOfType<KtReturnExpression>() ?: return false
return isIfConditionGuardClause(returnExpr) || returnExpr.isElvisOperatorGuardClause()
}
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.
right
I didn't notice that I no longer use the receiver 😕
I'll change the code
detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/ReturnCountSpec.kt
Outdated
Show resolved
Hide resolved
…/style/ReturnCountSpec.kt Co-authored-by: Nicola Corti <corti.nico@gmail.com>
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 okay with that change, although we have to be careful here.
Therefore, this change should be properly tested. The following script is very useful in that regard.
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.
Very nice PR @pinkasey . We definitely missed out on checking the braces.
Please see my proposed change, thx!
@@ -24,8 +24,8 @@ fun KtNamedFunction.yieldStatementsSkippingGuardClauses(): Sequence<KtExpression | |||
|
|||
fun KtExpression.isGuardClause(): Boolean { | |||
|
|||
fun KtReturnExpression.isIfConditionGuardClause(): Boolean { | |||
val ifExpr = this.parent?.parent as? KtIfExpression | |||
fun KtReturnExpression.isIfConditionGuardClause(ancestorExpression: KtExpression): Boolean { |
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.
@cortinico is right here. As you are not using the receiver inside the nested method is it unused.
I would propose an additional change which checks if the return statement is actually the last statement inside the if:
fun KtExpression.isGuardClause(): Boolean {
fun isIfConditionGuardClause(returnExpr: KtReturnExpression): Boolean {
val ifExpr = this as? KtIfExpression
return ifExpr != null &&
ifExpr.`else` == null &&
returnExpr === ifExpr.then?.lastBlockStatementOrThis()
}
fun KtReturnExpression.isElvisOperatorGuardClause(): Boolean {
val elvisExpr = this.parent as? KtBinaryExpression
return elvisExpr != null && elvisExpr.operationToken == KtTokens.ELVIS
}
val returnExpr = this.findDescendantOfType<KtReturnExpression>() ?: return false
return isIfConditionGuardClause(returnExpr) || returnExpr.isElvisOperatorGuardClause()
}
@@ -89,7 +151,10 @@ class ReturnCountSpec : Spek({ | |||
if(a == null) return | |||
val models = b as? Int ?: return | |||
val position = c?.takeIf { it != -1 } ?: return | |||
if(b !is String) return | |||
if(b !is 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.
I thought -
why not include one with braces too
tests fail because my example code (which i copy-pasted from previous it) - did not compile. |
OK |
4 -> return 4 | ||
} | ||
return 6 |
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.
These code-snippets didn't compile, because there wasn't a return
in every execution path.
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!
Thanks @arturbosch |
@arturbosch
I thought opening a PR was better than opening an Issue.
Although, I didn't properly test it yet - I'm new to gradle and Spek.
If you prefere, I can open an issue instead...
What I want -
to consider an
if
with a body as guard clause as well.I've added a test-case to demonstrate.
In my company, we almost disabled
ReturnCount
because it catches guard-clauses, and then I saw guard-clauses - and was disappointed it applies only toif
s without a body.Our code-style convention don't allow if's without body.