-
-
Notifications
You must be signed in to change notification settings - Fork 779
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
UnnecessaryParentheses: add options to allow in ambiguous cases #4881
Conversation
Codecov Report
@@ Coverage Diff @@
## main #4881 +/- ##
============================================
- Coverage 84.83% 84.81% -0.03%
- Complexity 3512 3517 +5
============================================
Files 497 497
Lines 11549 11585 +36
Branches 2139 2153 +14
============================================
+ Hits 9798 9826 +28
- Misses 686 687 +1
- Partials 1065 1072 +7
Continue to review full report at Codecov.
|
|
||
assertThat(UnnecessaryParentheses(config).lint(code)).hasSize(if (allow) 0 else 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.
What would happen with this cases:
val local = (1 * 2)
val local = (1 * 2 + 3)
val local = (a || b)
val local = (a || b && c)
val local = (a || b) || c
To me all of them should be flagged always.
@Configuration("whether to allow for potentially ambiguous boolean operations such as (x && y) || z") | ||
private val allowInAmbiguousBooleanExpressions: Boolean by config(defaultValue = false) | ||
|
||
@Configuration("whether to allow for potentially ambiguous numeric operations such as (x * y) + z") | ||
private val allowInAmbiguousNumericExpressions: Boolean by config(defaultValue = false) |
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 naming is (sadly) ambiguous.
It's not immediately clear what's the semantic of AmbiguousBooleanExpressions
and AmbiguousNumericExpressions
and we're forcing the user to read the example.
I would try to find a better name if possible.
Agreed on both points, this PR was meant more as a concept that something that's quite ready for production. As long as there are no fundamental objections to the idea here, I'll move it to a draft and take another stab at cleaning it up properly soon (maybe over the coming weekend). Sorry for the noise in the meantime. |
It's not noise! Thanks for proposing this and create a first draft to give fast feedback. This is really help us. It doesn't feel good to decline or force someone to change nearly all the code that s/he work for weeks. But we need to do those type of things to keep the project healthy and "managable". So don't worry about sending drafts of ideas we always welcome any help. |
Don't worry about that :) We're more than happy to receive contribs like yours. |
OK, I have a new approach heavily modified from IntelliJ's inspection https://github.com/JetBrains/intellij-community/blob/master/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/UnclearPrecedenceOfBinaryExpressionInspection.kt which is more generic and should (I believe) cleanly cover cases where parentheses are not strictly required but can be very helpful for clarifying the meaning of the code. This might even open the door for the inverse warning matching the behavior of IntelliJ's inspection: warning when parentheses could be helpful to clarify precedence but aren't currently present. I don't feel strongly about that idea - probably wouldn't use it myself but perhaps others would. Is there a standard way of unit testing different rule configurations that I should use here? I wanted to test all the existing cases with the new option applied to make sure it wouldn't change their behavior, but this added a fair bit of boilerplate. Perhaps |
I think that to test this properly you could move that code out of the rule. And test the function itself. There you can use a parameterized test to add all the cases you want. Given x code the function should return (true/false). Then you integrate that function inside the rule and you don't need to test all the cases there, just some to check that the integration is correct. Edit: I like this new approach a lot. It |
One blocker for adopting the UnnecessaryParentheses is that in some common cases parentheses that may not be strictly required by order of operations are still enormously helpful in parsing code, such as for boolean operations like `(x && y) || z`. While it's possible to `@Suppress` each of these, this adds bloat and encourages developers to avoid these (in my opinion) often very helpful indicators of code flow. Instead, we can add a configuration option to UnnecessaryParentheses to skip over some of the most common cases where unnecessary parentheses can be useful in clarifying the code. This strategy was adopted from IntelliJ's UnclearPrecedenceOfBinaryExpressionInspection inspection: https://github.com/JetBrains/intellij-community/blob/master/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/UnclearPrecedenceOfBinaryExpressionInspection.kt with a fair amount of trimming and a different set of operators that have unclear precedence (adding numeric and boolean ones).
Good ideas - I've refactored a bit and went with a different testing strategy using |
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 like it :)
There are a lot of tests but I think that's good because there are a lot of cases and I think that you are cheking all of them.
Really good job 👍
I'm moving this to |
This got me, the documentation says the flag exists, but when trying to use it, it fails to execute. Is this normal @BraisGabin that the docs don't match the latest release? |
@dzirbel I would like to throw another case at you where clarifying parentheses might help: private fun fakeClock(fixture: JFixture, minOffset: Long, maxOffset: Long): Clock? {
val now = System.currentTimeMillis()
val millis = fixture.buildRange((now - minOffset)..(now + maxOffset))
val zone = fixture.create().fromList(*ZoneId.getAvailableZoneIds().toTypedArray())
return Clock.fixed(Instant.ofEpochMilli(millis), ZoneId.of(zone))
} Without parens |
detekt#4881) One blocker for adopting the UnnecessaryParentheses is that in some common cases parentheses that may not be strictly required by order of operations are still enormously helpful in parsing code, such as for boolean operations like `(x && y) || z`. While it's possible to `@Suppress` each of these, this adds bloat and encourages developers to avoid these (in my opinion) often very helpful indicators of code flow. Instead, we can add a configuration option to UnnecessaryParentheses to skip over some of the most common cases where unnecessary parentheses can be useful in clarifying the code. This strategy was adopted from IntelliJ's UnclearPrecedenceOfBinaryExpressionInspection inspection: https://github.com/JetBrains/intellij-community/blob/master/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/UnclearPrecedenceOfBinaryExpressionInspection.kt with a fair amount of trimming and a different set of operators that have unclear precedence (adding numeric and boolean ones).
I believe that is the current behavior, yes - new rules, params, etc appear on the website immediately after they are merged rather than when a new version is released. I've always thought a version dropdown like Gradle and others use could be helpful.
I agree, that's a good case. Opened #5143 to add it. |
Follow-up to #4881 to allow parentheses clarifying usage of the range operator (`..`). Since for example `IntRange` has a `plus(Int)` operator function, the expression `a..b +c` could be ambiguous (but is interpreted as `a..(b + c)`). We can allow clarifying parentheses for such cases.
Yes, we need a versioned documentation. We know that docusaurus let us handle it but we didn't have the time for it: #4168. Any help there is more than appreciatted. |
I can take a stab at #4168 |
One blocker for adopting the UnnecessaryParentheses is that in some common cases parentheses that may not be strictly required by order of operations are still enormously helpful in parsing code, such as for boolean operations like
(x && y) || z
. While it's possible to@Suppress
each of these, this adds bloat and encourages developers to avoid these (in my opinion) often very helpful indicators of code flow.Instead, we can add a configuration option to UnnecessaryParentheses to skip over some of the most common cases where unnecessary parentheses can be useful in clarifying the code. This strategy was adopted from IntelliJ's UnclearPrecedenceOfBinaryExpressionInspection inspection: https://github.com/JetBrains/intellij-community/blob/master/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/UnclearPrecedenceOfBinaryExpressionInspection.kt with a fair amount of trimming and a different set of operators that have unclear precedence (adding numeric and boolean ones).
This idea has been discussed a number of times; most recently in #4495, which suggested that it could integrated into detekt (although perhaps only by porting the functionality from IntelliJ). Previous issues such as #2789 and #1929 concluded that it would be too much of an investment or overcomplicate the rule. I'm happy to stick with that precedent, but as a user who doesn't want to adopt this rule on my team until we can avoid at least some of the more common cases where parentheses can be helpful I thought I'd suggest a concrete implementation with fairly low complexity (although admittedly only addressing some of the lowest hanging fruit). I haven't thought through every edge case or application, so perhaps complexity would explode with a more fleshed-out version.