Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CascadingCallWrapping style rule (#4979)
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.
- Loading branch information
Showing
16 changed files
with
324 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
...es-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/CascadingCallWrapping.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package io.gitlab.arturbosch.detekt.rules.style | ||
|
||
import io.gitlab.arturbosch.detekt.api.CodeSmell | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Debt | ||
import io.gitlab.arturbosch.detekt.api.Entity | ||
import io.gitlab.arturbosch.detekt.api.Issue | ||
import io.gitlab.arturbosch.detekt.api.Rule | ||
import io.gitlab.arturbosch.detekt.api.Severity | ||
import io.gitlab.arturbosch.detekt.api.config | ||
import io.gitlab.arturbosch.detekt.api.internal.Configuration | ||
import org.jetbrains.kotlin.lexer.KtTokens | ||
import org.jetbrains.kotlin.psi.KtBinaryExpression | ||
import org.jetbrains.kotlin.psi.KtExpression | ||
import org.jetbrains.kotlin.psi.KtQualifiedExpression | ||
import org.jetbrains.kotlin.psi.KtUnaryExpression | ||
|
||
/** | ||
* Requires that all chained calls are placed on a new line if a preceding one is. | ||
* | ||
* <noncompliant> | ||
* foo() | ||
* .bar().baz() | ||
* </noncompliant> | ||
* | ||
* <compliant> | ||
* foo().bar().baz() | ||
* | ||
* foo() | ||
* .bar() | ||
* .baz() | ||
* </compliant> | ||
*/ | ||
class CascadingCallWrapping(config: Config = Config.empty) : Rule(config) { | ||
override val issue = Issue( | ||
id = javaClass.simpleName, | ||
severity = Severity.Style, | ||
description = "If a chained call is wrapped to a new line, subsequent chained calls should be as well.", | ||
debt = Debt.FIVE_MINS, | ||
) | ||
|
||
@Configuration("require trailing elvis expressions to be wrapped on a new line") | ||
private val includeElvis: Boolean by config(true) | ||
|
||
override fun visitQualifiedExpression(expression: KtQualifiedExpression) { | ||
super.visitQualifiedExpression(expression) | ||
|
||
checkExpression(expression, callExpression = expression.selectorExpression) | ||
} | ||
|
||
override fun visitBinaryExpression(expression: KtBinaryExpression) { | ||
super.visitBinaryExpression(expression) | ||
|
||
if (includeElvis && expression.operationToken == KtTokens.ELVIS) { | ||
checkExpression(expression, callExpression = expression.right) | ||
} | ||
} | ||
|
||
private fun checkExpression(expression: KtExpression, callExpression: KtExpression?) { | ||
if (!expression.containsNewline() && expression.receiverContainsNewline()) { | ||
val callTextOrEmpty = callExpression?.text?.let { " `$it`" }.orEmpty() | ||
report( | ||
CodeSmell( | ||
issue = issue, | ||
entity = Entity.from(expression), | ||
message = "Chained call$callTextOrEmpty should be wrapped to a new line since preceding calls were." | ||
) | ||
) | ||
} | ||
} | ||
|
||
@Suppress("ReturnCount") | ||
private fun KtExpression.containsNewline(): Boolean { | ||
val lhs: KtExpression | ||
val rhs: KtExpression | ||
|
||
when (this) { | ||
is KtQualifiedExpression -> { | ||
lhs = receiverExpression | ||
rhs = selectorExpression ?: return false | ||
} | ||
is KtBinaryExpression -> { | ||
if (operationToken != KtTokens.ELVIS) return false | ||
lhs = left ?: return false | ||
rhs = right ?: return false | ||
} | ||
else -> return false | ||
} | ||
|
||
val receiverEnd = lhs.startOffsetInParent + lhs.textLength | ||
val selectorStart = rhs.startOffsetInParent | ||
|
||
return (receiverEnd until selectorStart).any { text[it] == '\n' } | ||
} | ||
|
||
private fun KtExpression.receiverContainsNewline(): Boolean { | ||
val lhs = when (this) { | ||
is KtQualifiedExpression -> receiverExpression | ||
is KtBinaryExpression -> left ?: return false | ||
else -> return false | ||
} | ||
|
||
return when (lhs) { | ||
is KtQualifiedExpression -> lhs.containsNewline() | ||
is KtUnaryExpression -> (lhs.baseExpression as? KtQualifiedExpression)?.containsNewline() == true | ||
else -> false | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.