-
-
Notifications
You must be signed in to change notification settings - Fork 821
Add CascadingCallWrapping style rule #4979
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
Conversation
|
Thank you for this rule! I've been wanting this for a while but didn't get around to writing it. I'll review in more detail later. One thought: would you consider adding a configuration where there is a maximum number of chained calls on a single line? E.g. allow only 2 on a single line as a maximum, then require wrapping? |
I'd had the same thought but didn't add it initially since I thought it might add too much complication. Happy to add it here, but it raises the question of how it would be configured, e.g. Another piece I was thinking about is whether to require wrapping for any calls that are split across multiple lines; there's a test case for the current behavior but personally I'd tend to wrap each of those calls. I was also leaning toward enforcing that in a different rule to avoid making this one too heavy but it also has a natural connection. |
3flex
left a comment
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 is great! I'm a big fan of this rule, thanks again for implementing.
...s-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/CascadingCallWrappingSpec.kt
Outdated
Show resolved
Hide resolved
...s-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/CascadingCallWrappingSpec.kt
Outdated
Show resolved
Hide resolved
|
Good point on the configuration concerns, I hadn't thought that far ahead. A separate rule probably makes sense - if it just checks if there are x chained calls and that's over the y threshold then it would report a violation and tell you to split over multiple lines. Then this rule would still be there to make sure that if the chain calls are split, then all of them are on a unique line. That makes sense.
I think that behaviour is fine and reflects this semi-official guidance: Kotlin/kotlin-style-guide#9 (comment) |
Thanks for the link - didn't realize there was a project for contribution to the style guide! I think using breaks probably depends on the number of chained calls but IMO it's still subjective at best, so I'm also leaning toward not adding it as a rule. |
| assertThat(findings).hasSize(1) | ||
|
|
||
| val finding = findings.first() | ||
| assertEquals(TextLocation(start = 8, end = 30), finding.charPosition) | ||
| assertEquals( | ||
| "Chained call `plus(0)` should be wrapped to a new line since preceding calls were.", | ||
| finding.message, | ||
| ) |
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.
Please do not use junit assertions
| assertThat(findings).hasSize(1) | |
| val finding = findings.first() | |
| assertEquals(TextLocation(start = 8, end = 30), finding.charPosition) | |
| assertEquals( | |
| "Chained call `plus(0)` should be wrapped to a new line since preceding calls were.", | |
| finding.message, | |
| ) | |
| assertThat(findings) | |
| .hasSize(1) | |
| .hasTextLocations(8 to 30) | |
| assertThat(findings.first()) | |
| .hasMessage("Chained call `plus(0)` should be wrapped to a new line since preceding calls were.") |
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.
Good call, I'll do you one better by using first() on the assertion
| } | ||
| } | ||
|
|
||
| @Suppress("CanBeNonNullable") // false positive: callExpression cannot be made non-nullable |
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 tend to agree with detekt here ;) In which circumstances would the callExpression actually be null?
If the callExpresssion actually is null, this will mess up the error message. IMO it would be better to verify on the call side that the parameter is actually non null.
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 this is a clear false positive; callExpression can't just be made non-nullable - it can be null when either KtQualifiedExpression.selectorExpression or KtBinaryExpression.right are null (which I've never seen in practice, but they are both declared nullable), and the function isn't a no-op when it is null.
But your point still stands that the error message will be less useful if it is null. I don't think the solution is to verify it on the call side since I think we'll still want to generate a warning even if the right expression is missing. I'll improve the error message for the null case to just omit it.
| private fun String.containsInRange(needle: Char, startIndex: Int, endIndex: Int): Boolean { | ||
| for (i in startIndex until endIndex) { | ||
| if (this[i] == needle) { | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| return 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.
How about?
private fun String.containsInRange(needle: Char, range: IntRange): Boolean {
return range.any { this[it] == needle }
}or
private fun String.containsInRange(needle: Char, startIndex: Int, endIndex: Int): Boolean {
return (startIndex until endIndex).any { this[it] == needle }
}Feel free to ignore this comment ;)
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.
Good idea; this was probably a premature optimization to start with but using your second idea at the call site (since it's only used once) makes it cleaner.
Add a new rule CascadingCallWrapping which requires that if a chained call is placed on a newline then all subsequent calls must be as well, improving readability of long chains.
cortinico
left a comment
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.
Nice rule :) Thanks for sending this over
BraisGabin
left a comment
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 rule 👏👏👏
Add a new rule MaxChainedCallsOnSameLine to limit the number of chained calls on placed on a single line. This works well alongside CascadingCallWrapping in #4979 to make long call chains more readable by wrapping them on new lines.
|
This is a useful rule, thanks for writing this. But there is a case this is annoying. Example: The |
|
Let me know if I should open an issue for this. |
|
I don't agree with that but please, open an issue so other people can give their opinions |
Add a new rule CascadingCallWrapping which requires that if a chained call is placed on a newline then all subsequent calls must be as well, improving readability of long chains.
The rule name was chosen to be less ambiguous with ktlint's
ChainWrapping, but could possibly be improved. I've taken the liberty of enabling it in detekt's own configuration file and fix a handful of existing issues.