Skip to content
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

New Rule: BracesOnIfStatements with configurable parameters #5700

Merged
merged 35 commits into from Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f69c758
Initial commit
VitalyVPinchuk Jan 13, 2023
1ca4daa
Rework
VitalyVPinchuk Jan 17, 2023
8f4d25b
Rework
VitalyVPinchuk Jan 18, 2023
d04ac51
Rework
VitalyVPinchuk Jan 20, 2023
5075b39
Add braces check on if statements
VitalyVPinchuk Jan 23, 2023
747b158
Start rewriting tests
TWiStErRob Jan 25, 2023
57331f6
BROKEN COMPILER
TWiStErRob Jan 25, 2023
0fb41e1
fixes
TWiStErRob Jan 26, 2023
82adf9f
New approach
TWiStErRob Jan 26, 2023
f20db91
Fix changing `shouldCompileTestSnippets` default not taking effect.
TWiStErRob Jan 26, 2023
2172b1e
Fix infinite loop
TWiStErRob Jan 26, 2023
1b95194
Restructure everything
TWiStErRob Jan 26, 2023
77c8d7f
Fill in some numbers
TWiStErRob Jan 26, 2023
246f1ce
Merge pull request #6 from TWiStErRob/braces-history
VitalyVPinchuk Jan 27, 2023
e6b16db
Fix after merge
VitalyVPinchuk Jan 29, 2023
6559504
Deprecate MandatoryBracesIfStatements rule
VitalyVPinchuk Jan 31, 2023
788e367
Deprecate MandatoryBracesIfStatements rule
VitalyVPinchuk Feb 2, 2023
305edfb
Detekt codebase uses AssertJ, let's use those methods.
TWiStErRob Jan 28, 2023
1613f16
Speed up compile-test-snippets=true executions by compiling each code…
TWiStErRob Feb 18, 2023
bd1298e
Format
TWiStErRob Feb 18, 2023
8efea30
Remove duplicate code, and let the testCombinations method be used in…
TWiStErRob Feb 18, 2023
f9960f7
Add some more test cases (singleLine)
TWiStErRob Feb 18, 2023
a436418
Add some more test cases (multiLine)
TWiStErRob Feb 18, 2023
1309b16
Add some more test cases
TWiStErRob Feb 18, 2023
c0926c6
Fix after merge
VitalyVPinchuk Feb 2, 2023
44455ef
Merge remote-tracking branch 'upstream/braces' into braces
VitalyVPinchuk Feb 19, 2023
12bd699
Fix tests
VitalyVPinchuk Feb 19, 2023
9f6732c
Update detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt…
VitalyVPinchuk Feb 19, 2023
006ae3d
Add some more test cases and fix recursion
TWiStErRob Feb 20, 2023
9d2b90f
Group nested tests into @Nested
TWiStErRob Feb 20, 2023
aa76321
Remove trailing whitespace
TWiStErRob Feb 20, 2023
beb8656
Documentation of rule and internals.
TWiStErRob Feb 20, 2023
446a9cb
Simplify duplicate code.
TWiStErRob Feb 20, 2023
d172b9b
Fix detekt
TWiStErRob Feb 20, 2023
4d97f6c
Add missing tests for necessary policy
TWiStErRob Feb 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/detekt/detekt.yml
Expand Up @@ -149,6 +149,10 @@ potential-bugs:
active: true

style:
VitalyVPinchuk marked this conversation as resolved.
Show resolved Hide resolved
BracesOnIfStatements:
active: true
singleLine: 'consistent'
multiLine: 'consistent'
VitalyVPinchuk marked this conversation as resolved.
Show resolved Hide resolved
CanBeNonNullable:
active: true
CascadingCallWrapping:
Expand Down
4 changes: 4 additions & 0 deletions detekt-core/src/main/resources/default-detekt-config.yml
Expand Up @@ -500,6 +500,10 @@ style:
active: true
AlsoCouldBeApply:
active: false
BracesOnIfStatements:
active: false
singleLine: 'never'
multiLine: 'always'
CanBeNonNullable:
active: false
CascadingCallWrapping:
Expand Down
@@ -0,0 +1,136 @@
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.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.psi.psiUtil.siblings

/**
VitalyVPinchuk marked this conversation as resolved.
Show resolved Hide resolved
* This rule detects `if` statements which do not comply with the specified rules.
* Keeping braces consistent would improve readability and avoid possible errors.
*
VitalyVPinchuk marked this conversation as resolved.
Show resolved Hide resolved
* <noncompliant>
* val i = 1
* if (i > 0)
* println(i)
* else {
* println(-i)
* }
* </noncompliant>
*
* <compliant>
VitalyVPinchuk marked this conversation as resolved.
Show resolved Hide resolved
* || singleLine = 'never' multiLine = 'always'
VitalyVPinchuk marked this conversation as resolved.
Show resolved Hide resolved
* val x = if (condition) 5 else 4
* val x = if (condition) {
* 5
* } else {
* 4
* }
* </compliant>
*/
class BracesOnIfStatements(config: Config = Config.empty) : Rule(config) {
VitalyVPinchuk marked this conversation as resolved.
Show resolved Hide resolved

override val issue = Issue(
javaClass.simpleName,
Severity.Style,
"Braces do not comply with the specified policy",
Debt.FIVE_MINS
)

@Configuration("single-line braces policy")
private val singleLine: String by config(BRACES_NEVER)

@Configuration("multi-line braces policy")
private val multiLine: String by config(BRACES_ALWAYS)
VitalyVPinchuk marked this conversation as resolved.
Show resolved Hide resolved

override fun visitIfExpression(expression: KtIfExpression) {
VitalyVPinchuk marked this conversation as resolved.
Show resolved Hide resolved
super.visitIfExpression(expression)
TWiStErRob marked this conversation as resolved.
Show resolved Hide resolved

val isMultiline = isMultiline(expression.ifKeyword)

val thenExpression = expression.then ?: return
if (!isCompliant(thenExpression, isMultiline)) {
report(CodeSmell(issue, Entity.from(expression.ifKeyword), issue.description))
}

val elseExpression = expression.`else` ?: return
if (!shouldSkip(elseExpression) && !isCompliant(elseExpression, isMultiline)) {
report(CodeSmell(issue, Entity.from(expression.elseKeyword ?: elseExpression), issue.description))
}

if (!isConsistent(thenExpression, elseExpression, isMultiline)) {
report(CodeSmell(issue, Entity.from(expression.ifKeyword), "Inconsistent braces"))
}
}

private fun isCompliant(expression: KtExpression, isMultiline: Boolean): Boolean = when {
isMultiStatement(expression) -> {
true
}

isMultiline && (multiLine == BRACES_ALWAYS) || !isMultiline && (singleLine == BRACES_ALWAYS) -> {
VitalyVPinchuk marked this conversation as resolved.
Show resolved Hide resolved
expression is KtBlockExpression
}

isMultiline && (multiLine == BRACES_NEVER) || !isMultiline && (singleLine == BRACES_NEVER) -> {
expression !is KtBlockExpression
}

else -> {
true
}
}

private fun isConsistent(
thenExpression: KtExpression,
elseExpression: KtExpression,
isMultiline: Boolean
): Boolean = when {
shouldSkip(elseExpression) -> {
true
}

isMultiline && (multiLine == BRACES_CONSISTENT) || !isMultiline && (singleLine == BRACES_CONSISTENT) -> {
thenExpression is KtBlockExpression && elseExpression is KtBlockExpression ||
thenExpression !is KtBlockExpression && elseExpression !is KtBlockExpression
}

else -> {
true
}
}

private fun isMultiStatement(expression: KtExpression): Boolean {
if (expression !is KtBlockExpression) return false

return expression
.firstChild
.siblings(forward = true, withItself = false)
.count { it is KtExpression } > 0
TWiStErRob marked this conversation as resolved.
Show resolved Hide resolved
}

private fun isMultiline(element: PsiElement?): Boolean {
if (element == null) return false
return element.siblings().any { it.textContains('\n') }
TWiStErRob marked this conversation as resolved.
Show resolved Hide resolved
}

private fun shouldSkip(expression: KtExpression): Boolean =
expression is KtIfExpression || expression is KtWhenExpression

companion object {
private const val BRACES_ALWAYS = "always"
private const val BRACES_NEVER = "never"
private const val BRACES_CONSISTENT = "consistent"
}
}
Expand Up @@ -77,6 +77,7 @@ class StyleGuideProvider : DefaultRuleSetProvider {
MayBeConst(config),
PreferToOverPairSyntax(config),
MandatoryBracesIfStatements(config),
BracesOnIfStatements(config),
MandatoryBracesLoops(config),
NullableBooleanCheck(config),
VarCouldBeVal(config),
Expand Down