diff --git a/documentation/snapshot/docs/rules/experimental.md b/documentation/snapshot/docs/rules/experimental.md
index 287503a810..00c0e49950 100644
--- a/documentation/snapshot/docs/rules/experimental.md
+++ b/documentation/snapshot/docs/rules/experimental.md
@@ -75,6 +75,87 @@ Wraps binary expression at the operator reference whenever the binary expression
Rule id: `binary-expression-wrapping` (`standard` rule set)
+## Blank lines between when-conditions
+
+Consistently add or remove blank lines between when-conditions in a when-statement. A blank line is only added between when-conditions if the when-statement contains at lease one multiline when-condition. If a when-statement only contains single line when-conditions, then the blank lines between the when-conditions are removed.
+
+!!! note
+ Ktlint uses `.editorconfig` property `ij_kotlin_line_break_after_multiline_when_entry` but applies it also on single line entries to increase consistency.
+
+=== "[:material-heart:](#) Ktlint"
+
+ ```kotlin
+ val foo1 =
+ when (bar) {
+ BAR1 -> "bar1"
+ BAR2 -> "bar2"
+ else -> null
+ }
+
+ // ij_kotlin_line_break_after_multiline_when_entry = true
+ val foo2 =
+ when (bar) {
+ BAR1 -> "bar1"
+
+ BAR2 -> {
+ "bar2"
+ }
+
+ else -> null
+ }
+
+ // ij_kotlin_line_break_after_multiline_when_entry = false
+ val foo2 =
+ when (bar) {
+ BAR1 -> "bar1"
+ BAR2 -> {
+ "bar2"
+ }
+ else -> null
+ }
+ ```
+=== "[:material-heart-off-outline:](#) Disallowed"
+
+ ```kotlin
+ // ij_kotlin_line_break_after_multiline_when_entry = true | false (no blank lines in simple when-statement)
+ val foo1 =
+ when (bar) {
+ BAR1 -> "bar1"
+
+ BAR2 -> "bar2"
+
+ else -> null
+ }
+
+ // ij_kotlin_line_break_after_multiline_when_entry = true (missing newline after BAR1)
+ val foo2 =
+ when (bar) {
+ BAR1 -> "bar1"
+ BAR2 -> {
+ "bar2"
+ }
+
+ else -> null
+ }
+
+ // ij_kotlin_line_break_after_multiline_when_entry = false (unexpected newline after BAR2)
+ val foo2 =
+ when (bar) {
+ BAR1 -> "bar1"
+ BAR2 -> {
+ "bar2"
+ }
+
+ else -> null
+ }
+ ```
+
+| Configuration setting | ktlint_official | intellij_idea | android_studio |
+|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------:|:-------------:|:--------------:|
+| `ij_kotlin_line_break_after_multiline_when_entry`
Despite its name, forces a blank line between single line and multiline when-entries when at least one multiline when-entry is found in the when-statement. | `true` | `true` | `true` |
+
+Rule id: `blank-lines-between-when-conditions` (`standard` rule set)
+
## Chain method continuation
In a multiline method chain, the chain operators (`.` or `?.`) have to be aligned with each other.
diff --git a/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporter.kt b/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporter.kt
index 98400cf7d2..62e979f297 100644
--- a/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporter.kt
+++ b/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporter.kt
@@ -29,6 +29,7 @@ public class FormatReporter(
countAutoCorrectPossibleOrDone.putIfAbsent(file, 0)
countAutoCorrectPossibleOrDone.replace(file, countAutoCorrectPossibleOrDone.getOrDefault(file, 0) + 1)
}
+
else -> {
countCanNotBeAutoCorrected.putIfAbsent(file, 0)
countCanNotBeAutoCorrected.replace(file, countCanNotBeAutoCorrected.getOrDefault(file, 0) + 1)
@@ -46,18 +47,21 @@ public class FormatReporter(
} else {
"Format required (1 violation needs manual fixing)"
}
+
canNotBeAutocorrected > 1 ->
if (format) {
"Format not completed ($canNotBeAutocorrected violations need manual fixing)"
} else {
"Format required ($canNotBeAutocorrected violations need manual fixing)"
}
+
countAutoCorrectPossibleOrDone.getOrDefault(file, 0) > 0 ->
if (format) {
"Format completed (all violations have been fixed)"
} else {
"Format required (all violations can be autocorrected)"
}
+
else ->
"Format not needed (no violations found)"
}
diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/FileUtils.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/FileUtils.kt
index a480371f74..27e2a9bd67 100644
--- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/FileUtils.kt
+++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/FileUtils.kt
@@ -160,8 +160,10 @@ private fun Path.findCommonParentDir(path: Path): Path =
when {
path.startsWith(this) ->
this
+
startsWith(path) ->
path
+
else ->
this@findCommonParentDir.findCommonParentDir(path.parent)
}
diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt
index 039e47469c..8aab056cbd 100644
--- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt
+++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt
@@ -499,6 +499,7 @@ internal class KtlintCommandLine :
}.also { formattedFileContent ->
when {
code.isStdIn -> print(formattedFileContent)
+
code.content != formattedFileContent ->
code
.filePath
@@ -616,6 +617,7 @@ internal class KtlintCommandLine :
detail = "Not a valid Kotlin file (${e.message?.lowercase(Locale.getDefault())})",
status = KOTLIN_PARSE_EXCEPTION,
)
+
is KtLintRuleException -> {
logger.debug(e) { "Internal Error (${e.ruleId}) in ${code.fileNameOrStdin()} at position '${e.line}:${e.col}" }
KtlintCliError(
@@ -630,6 +632,7 @@ internal class KtlintCommandLine :
status = KTLINT_RULE_ENGINE_EXCEPTION,
)
}
+
else -> throw e
}
}
diff --git a/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/CommandLineTestRunner.kt b/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/CommandLineTestRunner.kt
index 5c3319efd6..8841bba05e 100644
--- a/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/CommandLineTestRunner.kt
+++ b/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/CommandLineTestRunner.kt
@@ -188,6 +188,7 @@ class CommandLineTestRunner(
key.equals(PATH, ignoreCase = true)
} ?: PATH
}
+
else -> PATH
}
environment[pathKey] = "$JAVA_HOME_BIN_DIR${File.pathSeparator}${OsEnvironment()[PATH]}"
diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/IndentConfig.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/IndentConfig.kt
index 6df4dc53d3..385d136ff9 100644
--- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/IndentConfig.kt
+++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/IndentConfig.kt
@@ -112,6 +112,7 @@ public class IndentConfig(
require(indent.matches(TABS_AND_SPACES))
return when (indentStyle) {
SPACE -> indent.replaceTabWithSpaces()
+
TAB -> {
"\t".repeat(indentLevelFrom(indent))
// Silently swallow spaces if not enough spaces present to convert to a tab
diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppression.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppression.kt
index 639028b2aa..8ba6536919 100644
--- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppression.kt
+++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppression.kt
@@ -73,14 +73,17 @@ private fun KtlintSuppressionAtOffset.offsetFromStartOf(code: String): Int {
col == 1 && codeLine.isEmpty() -> {
startOffsetOfLineContainingLintError
}
+
col <= codeLine.length -> {
startOffsetOfLineContainingLintError + (col - 1)
}
+
col == codeLine.length + 1 -> {
// Offset of suppression is set at EOL of the line. This is visually correct for the reader. But the newline character was stripped
// from the line because the lines were split using that character.
startOffsetOfLineContainingLintError + col
}
+
else -> {
throw KtlintSuppressionOutOfBoundsException(this)
}
diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/PositionInTextLocator.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/PositionInTextLocator.kt
index 5f96bbf34a..87e730930e 100644
--- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/PositionInTextLocator.kt
+++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/PositionInTextLocator.kt
@@ -59,6 +59,7 @@ private class SegmentTree(
): Int =
when {
l > r -> -1
+
else -> {
val i = l + (r - l) / 2
val s = segments[i]
diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt
index 715bcae262..b38fe6bc85 100644
--- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt
+++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt
@@ -220,6 +220,7 @@ internal object SuppressionLocatorBuilder {
.let { suppressedRuleIds ->
when {
suppressedRuleIds.isEmpty() -> null
+
suppressedRuleIds.contains(ALL_KTLINT_RULES_SUPPRESSION_ID) ->
SuppressionHint(
IntRange(startOffset, endOffset - 1),
@@ -244,6 +245,7 @@ internal object SuppressionLocatorBuilder {
// Disable all rules
ALL_KTLINT_RULES_SUPPRESSION_ID
}
+
argumentExpressionText.startsWith("ktlint:") -> {
// Disable specific rule. For backwards compatibility prefix rules without rule set id with the "standard" rule set
// id. Note that the KtlintSuppressionRule will emit a lint violation on the id. So this fix is only applicable for
@@ -252,6 +254,7 @@ internal object SuppressionLocatorBuilder {
.removePrefix("ktlint:")
.let { RuleId.prefixWithStandardRuleSetIdWhenMissing(it) }
}
+
else -> {
// Disable specific rule if the annotation value is mapped to a specific rule
annotationValueToRuleMapping[argumentExpressionText]
diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RuleExecutionRuleFilter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RuleExecutionRuleFilter.kt
index 6a79288a50..f79080f500 100644
--- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RuleExecutionRuleFilter.kt
+++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RuleExecutionRuleFilter.kt
@@ -96,10 +96,13 @@ private class RuleExecutionFilter(
when {
rule is Rule.Experimental && rule is Rule.OfficialCodeStyle ->
isExperimentalEnabled(rule) && isOfficialCodeStyleEnabled(rule)
+
rule is Rule.Experimental ->
isExperimentalEnabled(rule)
+
rule is Rule.OfficialCodeStyle ->
isOfficialCodeStyleEnabled(rule)
+
else ->
isRuleSetEnabled(rule)
}
diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt
index 920ec92ce9..1fbc943bf1 100644
--- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt
+++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt
@@ -506,6 +506,7 @@ private class AutoCorrectErrorRule :
(node as LeafElement).rawReplaceWithText(STRING_VALUE_AFTER_AUTOCORRECT)
}
}
+
STRING_VALUE_NOT_TO_BE_CORRECTED ->
emit(node.startOffset, ERROR_MESSAGE_CAN_NOT_BE_AUTOCORRECTED, false)
}
diff --git a/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api b/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api
index e858e4c22c..5c7c9c1356 100644
--- a/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api
+++ b/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api
@@ -72,6 +72,21 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDe
public static final fun getBLANK_LINE_BEFORE_DECLARATION_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId;
}
+public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions : com/pinterest/ktlint/ruleset/standard/StandardRule {
+ public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions$Companion;
+ public fun ()V
+ public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V
+ public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V
+}
+
+public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions$Companion {
+ public final fun getLINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY ()Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfigProperty;
+}
+
+public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditionsKt {
+ public static final fun getBLANK_LINE_BETWEEN_WHEN_CONDITIONS_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId;
+}
+
public final class com/pinterest/ktlint/ruleset/standard/rules/BlockCommentInitialStarAlignmentRule : com/pinterest/ktlint/ruleset/standard/StandardRule {
public fun ()V
public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRuleSetProvider.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRuleSetProvider.kt
index fccfe4505a..26a6b41dfd 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRuleSetProvider.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRuleSetProvider.kt
@@ -9,6 +9,7 @@ import com.pinterest.ktlint.ruleset.standard.rules.ArgumentListWrappingRule
import com.pinterest.ktlint.ruleset.standard.rules.BackingPropertyNamingRule
import com.pinterest.ktlint.ruleset.standard.rules.BinaryExpressionWrappingRule
import com.pinterest.ktlint.ruleset.standard.rules.BlankLineBeforeDeclarationRule
+import com.pinterest.ktlint.ruleset.standard.rules.BlankLineBetweenWhenConditions
import com.pinterest.ktlint.ruleset.standard.rules.BlockCommentInitialStarAlignmentRule
import com.pinterest.ktlint.ruleset.standard.rules.ChainMethodContinuationRule
import com.pinterest.ktlint.ruleset.standard.rules.ChainWrappingRule
@@ -109,6 +110,7 @@ public class StandardRuleSetProvider : RuleSetProviderV3(RuleSetId.STANDARD) {
RuleProvider { BackingPropertyNamingRule() },
RuleProvider { BinaryExpressionWrappingRule() },
RuleProvider { BlankLineBeforeDeclarationRule() },
+ RuleProvider { BlankLineBetweenWhenConditions() },
RuleProvider { BlockCommentInitialStarAlignmentRule() },
RuleProvider { ChainMethodContinuationRule() },
RuleProvider { ChainWrappingRule() },
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt
index 6358ba657e..2bdabde834 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt
@@ -118,17 +118,21 @@ public class AnnotationRule :
visitAnnotationList(node, emit, autoCorrect)
visitFileAnnotationList(node, emit, autoCorrect)
}
+
ANNOTATED_EXPRESSION, MODIFIER_LIST -> {
visitAnnotationList(node, emit, autoCorrect)
}
+
ANNOTATION -> {
// Annotation array
// @[...]
visitAnnotation(node, emit, autoCorrect)
}
+
ANNOTATION_ENTRY -> {
visitAnnotationEntry(node, emit, autoCorrect)
}
+
TYPE_ARGUMENT_LIST -> {
visitTypeArgumentList(node, emit, autoCorrect)
}
@@ -147,8 +151,10 @@ public class AnnotationRule :
when {
node.elementType == ANNOTATED_EXPRESSION ->
indentConfig.siblingIndentOf(node)
+
node.hasAnnotationBeforeConstructor() ->
indentConfig.siblingIndentOf(node.treeParent)
+
else ->
indentConfig.parentIndentOf(node)
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt
index 4b0db9ca09..23e04dc4ac 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt
@@ -147,7 +147,6 @@ public class ArgumentListWrappingRule :
// 2
// )
child.treeParent.hasTypeArgumentListInFront() -> -1
-
// IDEA quirk:
// foo
// .bar = Baz(
@@ -161,7 +160,6 @@ public class ArgumentListWrappingRule :
// 2
// )
child.treeParent.isPartOfDotQualifiedAssignmentExpression() -> -1
-
else -> 0
}.let {
if (child.treeParent.isOnSameLineAsControlFlowKeyword()) {
@@ -198,6 +196,7 @@ public class ArgumentListWrappingRule :
}
}
}
+
VALUE_ARGUMENT,
RPAR,
-> {
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt
new file mode 100644
index 0000000000..5978a9d6f1
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt
@@ -0,0 +1,149 @@
+package com.pinterest.ktlint.ruleset.standard.rules
+
+import com.pinterest.ktlint.rule.engine.core.api.ElementType
+import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHEN_ENTRY
+import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE
+import com.pinterest.ktlint.rule.engine.core.api.RuleId
+import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint
+import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL
+import com.pinterest.ktlint.rule.engine.core.api.children
+import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig
+import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty
+import com.pinterest.ktlint.rule.engine.core.api.indent
+import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace
+import com.pinterest.ktlint.rule.engine.core.api.lastChildLeafOrSelf
+import com.pinterest.ktlint.rule.engine.core.api.nextLeaf
+import com.pinterest.ktlint.rule.engine.core.api.prevCodeSibling
+import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe
+import com.pinterest.ktlint.ruleset.standard.StandardRule
+import org.ec4j.core.model.PropertyType
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+
+/**
+ * The Kotlin Coding Conventions suggest to consider using a blank line after a multiline when-condition
+ * (https://kotlinlang.org/docs/coding-conventions.html#control-flow-statements) which behavior is managed via '.editorconfig' property
+ * `ij_kotlin_line_break_after_multiline_when_entry`.
+ *
+ * Ktlint uses the property `ij_kotlin_line_break_after_multiline_when_entry` to consistently add/remove blank line between all
+ * when-conditions in the when-statement depending on whether the statement contains at least one multiline when-condition.
+ */
+@SinceKtlint("1.2.0", EXPERIMENTAL)
+public class BlankLineBetweenWhenConditions :
+ StandardRule(
+ id = "blank-line-between-when-conditions",
+ usesEditorConfigProperties = setOf(LINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY),
+ ) {
+ private var lineBreakAfterWhenCondition = LINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY.defaultValue
+
+ override fun beforeFirstNode(editorConfig: EditorConfig) {
+ lineBreakAfterWhenCondition = editorConfig[LINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY]
+ }
+
+ override fun beforeVisitChildNodes(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
+ ) {
+ if (node.elementType == ElementType.WHEN) {
+ visitWhenStatement(node, autoCorrect, emit)
+ }
+ }
+
+ private fun visitWhenStatement(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
+ ) {
+ val hasMultilineWhenCondition = node.hasAnyMultilineWhenCondition()
+ if (hasMultilineWhenCondition && lineBreakAfterWhenCondition) {
+ addBlankLinesBetweenWhenConditions(node, autoCorrect, emit)
+ } else {
+ removeBlankLinesBetweenWhenConditions(node, autoCorrect, emit)
+ }
+ }
+
+ private fun addBlankLinesBetweenWhenConditions(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
+ ) {
+ node
+ .children()
+ .filter { it.elementType == WHEN_ENTRY }
+ // Blank lines should only be added *between* when-conditions, so first when-condition is to be skipped
+ .drop(1)
+ .forEach { whenEntry ->
+ whenEntry
+ .findWhitespaceAfterPreviousCodeSibling()
+ ?.takeUnless { it.containsBlankLine() }
+ ?.let { whitespaceBeforeWhenEntry ->
+ emit(
+ whitespaceBeforeWhenEntry.startOffset + 1,
+ "Add a blank line between all when-condition in case at least one multiline when-condition is found in the " +
+ "statement",
+ true,
+ )
+ if (autoCorrect) {
+ whitespaceBeforeWhenEntry.upsertWhitespaceBeforeMe("\n${whenEntry.indent()}")
+ }
+ }
+ }
+ }
+
+ private fun ASTNode.containsBlankLine(): Boolean = elementType == WHITE_SPACE && text.count { it == '\n' } > 1
+
+ private fun ASTNode.hasAnyMultilineWhenCondition() = children().any { it.elementType == WHEN_ENTRY && it.textContains('\n') }
+
+ private fun ASTNode.findWhitespaceAfterPreviousCodeSibling() =
+ prevCodeSibling()
+ ?.lastChildLeafOrSelf()
+ ?.nextLeaf { it.isWhiteSpace() }
+
+ private fun removeBlankLinesBetweenWhenConditions(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
+ ) {
+ node
+ .children()
+ .filter { it.elementType == WHEN_ENTRY }
+ // Blank lines should only be removed *between* when-conditions, so first when-condition is to be skipped
+ .drop(1)
+ .forEach { whenEntry ->
+ whenEntry
+ .findWhitespaceAfterPreviousCodeSibling()
+ ?.takeIf { it.containsBlankLine() }
+ ?.let { whitespaceBeforeWhenEntry ->
+ emit(
+ whitespaceBeforeWhenEntry.startOffset + 1,
+ "Unexpected blank lines between when-condition if all when-conditions are single lines",
+ true,
+ )
+ if (autoCorrect) {
+ whitespaceBeforeWhenEntry.upsertWhitespaceBeforeMe("\n${whenEntry.indent(includeNewline = false)}")
+ }
+ }
+ }
+ }
+
+ public companion object {
+ private val BOOLEAN_VALUES_SET = setOf("true", "false")
+
+ public val LINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY: EditorConfigProperty =
+ EditorConfigProperty(
+ type =
+ PropertyType.LowerCasingPropertyType(
+ "ij_kotlin_line_break_after_multiline_when_entry",
+ "Defines whether a blank line is to be added after a when entry. Contrary to default IDEA formatting, " +
+ "ktlint adds the blank line between all when-conditions if the when-statement contains at least one " +
+ "multiline when-condition. Or, it removes all blank lines between the when-conditions if the when-statement " +
+ "does not contain any multiline when-condition.",
+ PropertyType.PropertyValueParser.BOOLEAN_VALUE_PARSER,
+ BOOLEAN_VALUES_SET,
+ ),
+ defaultValue = true,
+ )
+ }
+}
+
+public val BLANK_LINE_BETWEEN_WHEN_CONDITIONS_RULE_ID: RuleId = BlankLineBetweenWhenConditions().ruleId
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRule.kt
index c3dfa8fa76..3183473bad 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRule.kt
@@ -138,7 +138,9 @@ public class ChainWrappingRule :
// change the indent of the next line
prevLeaf
}
+
nextLeaf.isWhiteSpaceWithoutNewline() -> nextLeaf
+
else -> null
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt
index 9350dbc541..b08338807f 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt
@@ -67,6 +67,7 @@ public class ContextReceiverWrappingRule :
when {
node.elementType == CONTEXT_RECEIVER_LIST ->
visitContextReceiverList(node, autoCorrect, emit)
+
node.elementType == TYPE_ARGUMENT_LIST && node.isPartOf(CONTEXT_RECEIVER) ->
visitContextReceiverTypeArgumentList(node, autoCorrect, emit)
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRule.kt
index 4c7c2a9524..19dbf6fe87 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRule.kt
@@ -154,9 +154,11 @@ public class FunctionNamingRule :
ANNOTATION -> {
it.containsAnnotationEntryWithIdentifierIn(excludeWhenAnnotatedWith)
}
+
ANNOTATION_ENTRY -> {
it.annotationEntryName() in excludeWhenAnnotatedWith
}
+
else -> false
}
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRule.kt
index f84024c13a..d5e8ac318d 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRule.kt
@@ -104,12 +104,15 @@ public class IfElseBracingRule :
this == null -> {
false
}
+
this.elementType == BLOCK -> {
true
}
+
this.elementType == IF -> {
findChildByType(THEN).hasBracing() || findChildByType(ELSE).hasBracing()
}
+
else -> {
this.firstChildNode.hasBracing()
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt
index 666fa46e45..9c179dfe48 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt
@@ -222,6 +222,7 @@ public class ImportOrderingRule :
value,
"Import layout must contain at least one entry of a wildcard symbol (*)",
)
+
value == "idea" -> {
LOGGER.warn {
"`idea` is deprecated! Please use `*,java.**,javax.**,kotlin.**,^` instead to ensure that the Kotlin IDE " +
@@ -232,6 +233,7 @@ public class ImportOrderingRule :
IDEA_PATTERN,
)
}
+
value == "ascii" -> {
LOGGER.warn {
"`ascii` is deprecated! Please use `*` instead to ensure that the Kotlin IDE plugin recognizes the value"
@@ -241,6 +243,7 @@ public class ImportOrderingRule :
ASCII_PATTERN,
)
}
+
else ->
try {
PropertyType.PropertyValue.valid(
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveCommentsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveCommentsRule.kt
index 0d1b1a04b4..503d04a567 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveCommentsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveCommentsRule.kt
@@ -131,6 +131,7 @@ public class NoConsecutiveCommentsRule :
KDOC_START,
->
true
+
else ->
false
}
@@ -142,6 +143,7 @@ public class NoConsecutiveCommentsRule :
KDOC_END,
->
true
+
else ->
false
}
@@ -149,10 +151,13 @@ public class NoConsecutiveCommentsRule :
private fun ASTNode.commentType() =
when (this.elementType) {
EOL_COMMENT -> "an EOL comment"
+
BLOCK_COMMENT -> "a block comment"
+
KDOC_START,
KDOC_END,
-> "a KDoc"
+
else -> this.elementType.toString().lowercase()
}
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt
index 4dc21993e6..77886232a0 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt
@@ -48,6 +48,7 @@ public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") {
// Do not change the last line as it contains the indentation of the next element except
// when it is an EOL comment which may also not contain trailing spaces
line
+
line.hasTrailingSpace() -> {
val modifiedLine = line.trimEnd()
val firstTrailingSpaceOffset = violationOffset + modifiedLine.length
@@ -55,6 +56,7 @@ public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") {
violated = true
modifiedLine
}
+
else -> line
}
violationOffset += line.length + 1
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt
index c53ca9738b..83ffd8135d 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt
@@ -58,6 +58,7 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") {
val packageDirective = node.psi as KtPackageDirective
packageName = packageDirective.qualifiedName
}
+
IMPORT_DIRECTIVE -> {
val importPath = (node.psi as KtImportDirective).importPath!!
if (imports.containsKey(importPath)) {
@@ -70,11 +71,13 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") {
imports[importPath] = node
}
}
+
DOT_QUALIFIED_EXPRESSION -> {
if (node.isExpressionForStaticImportWithExistingParentImport()) {
parentExpressions.add(node.text.substringBeforeLast("("))
}
}
+
MARKDOWN_LINK -> {
node
.psi
@@ -85,6 +88,7 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") {
ref.add(Reference(linkText.split('.').last(), false))
}
}
+
REFERENCE_EXPRESSION, OPERATION_REFERENCE -> {
if (!node.isPartOf(IMPORT_DIRECTIVE)) {
val identifier =
@@ -106,6 +110,7 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") {
}
}
}
+
BY_KEYWORD -> foundByKeyword = true
}
}
@@ -208,11 +213,13 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") {
?.takeIf { it.isWhiteSpaceWithNewline() }
?.let { it.treeParent.removeChild(it) }
}
+
treeParent.lastChildNode == this -> {
prevSibling()
?.takeIf { it.isWhiteSpaceWithNewline() }
?.let { it.treeParent.removeChild(it) }
}
+
else -> {
nextLeaf(true)
?.takeIf { it.isWhiteSpaceWithNewline() }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt
index 8ca7fcc5a2..3e0a0610dc 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt
@@ -105,6 +105,7 @@ public class ParameterListSpacingRule :
replaceWithSingleSpace(el, emit, autoCorrect)
}
}
+
COMMA -> {
// Comma must be followed by whitespace
el
@@ -112,6 +113,7 @@ public class ParameterListSpacingRule :
?.takeIf { it.elementType != WHITE_SPACE }
?.let { addMissingWhiteSpaceAfterMe(el, emit, autoCorrect) }
}
+
VALUE_PARAMETER -> {
valueParameterCount += 1
visitValueParameter(el, emit, autoCorrect)
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt
index dc6074217c..764debc605 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt
@@ -132,12 +132,18 @@ public class ParameterListWrappingRule :
private fun ASTNode.needToWrapParameterList() =
when {
hasNoParameters() -> false
+
codeStyle != ktlint_official && isPartOfFunctionLiteralInNonKtlintOfficialCodeStyle() -> false
+
codeStyle == ktlint_official && containsAnnotatedParameter() -> true
+
codeStyle == ktlint_official && isPartOfFunctionLiteralStartingOnSameLineAsClosingParenthesisOfPrecedingReferenceExpression() ->
false
+
textContains('\n') -> true
+
isOnLineExceedingMaxLineLength() -> true
+
else -> false
}
@@ -219,7 +225,6 @@ public class ParameterListWrappingRule :
// param2: R
// )
child.treeParent.isFunWithTypeParameterListInFront() -> -1
-
else -> 0
}.let {
if (child.elementType == VALUE_PARAMETER) {
@@ -252,6 +257,7 @@ public class ParameterListWrappingRule :
}
}
}
+
VALUE_PARAMETER,
RPAR,
-> {
@@ -312,9 +318,12 @@ public class ParameterListWrappingRule :
private fun errorMessage(node: ASTNode) =
when (node.elementType) {
LPAR -> """Unnecessary newline before "(""""
+
VALUE_PARAMETER ->
"Parameter should start on a newline"
+
RPAR -> """Missing newline before ")""""
+
else -> throw UnsupportedOperationException()
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRule.kt
index 23fdbc8aa8..2a0da216ef 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRule.kt
@@ -50,9 +50,11 @@ public class PropertyNamingRule : StandardRule("property-naming") {
property.hasConstModifier() -> {
visitConstProperty(identifier, emit)
}
+
property.hasCustomGetter() || property.isTopLevelValue() || property.isObjectValue() -> {
// Can not reliably determine whether the value is immutable or not
}
+
else -> {
visitNonConstProperty(identifier, emit)
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRule.kt
index 9412ca36a1..5a9e620dc3 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRule.kt
@@ -151,12 +151,14 @@ public class SpacingAroundCurlyRule :
node.upsertWhitespaceAfterMe(" ")
}
}
+
!spacingBefore -> {
emit(node.startOffset, "Missing spacing before \"${node.text}\"", true)
if (autoCorrect) {
node.upsertWhitespaceBeforeMe(" ")
}
}
+
!spacingAfter -> {
emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true)
if (autoCorrect) {
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt
index 0c339c11e4..1462c3d322 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt
@@ -28,7 +28,9 @@ public class SpacingAroundDoubleColonRule : StandardRule("double-colon-spacing")
var removeSingleWhiteSpace = false
val spacingBefore =
when {
- node.isPartOf(CLASS_LITERAL_EXPRESSION) && prevLeaf is PsiWhiteSpace -> true // Clazz::class
+ node.isPartOf(CLASS_LITERAL_EXPRESSION) && prevLeaf is PsiWhiteSpace -> true
+
+ // Clazz::class
node.isPartOf(CALLABLE_REFERENCE_EXPRESSION) && prevLeaf is PsiWhiteSpace -> // String::length, ::isOdd
if (node.treePrev == null) { // compose(length, ::isOdd), val predicate = ::isOdd
removeSingleWhiteSpace = true
@@ -36,6 +38,7 @@ public class SpacingAroundDoubleColonRule : StandardRule("double-colon-spacing")
} else { // String::length, List::isEmpty
!prevLeaf.textContains('\n')
}
+
else -> false
}
val spacingAfter = nextLeaf is PsiWhiteSpace
@@ -47,12 +50,14 @@ public class SpacingAroundDoubleColonRule : StandardRule("double-colon-spacing")
nextLeaf!!.treeParent.removeChild(nextLeaf)
}
}
+
spacingBefore -> {
emit(prevLeaf!!.startOffset, "Unexpected spacing before \"${node.text}\"", true)
if (autoCorrect) {
prevLeaf.removeSelf(removeSingleWhiteSpace)
}
}
+
spacingAfter -> {
emit(nextLeaf!!.startOffset, "Unexpected spacing after \"${node.text}\"", true)
if (autoCorrect) {
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt
index 87ddecddf4..cb8959633c 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt
@@ -89,12 +89,14 @@ public class SpacingAroundOperatorsRule : StandardRule("op-spacing") {
node.upsertWhitespaceAfterMe(" ")
}
}
+
!spacingBefore -> {
emit(node.startOffset, "Missing spacing before \"${node.text}\"", true)
if (autoCorrect) {
node.upsertWhitespaceBeforeMe(" ")
}
}
+
!spacingAfter -> {
emit(node.startOffset + node.textLength, "Missing spacing after \"${node.text}\"", true)
if (autoCorrect) {
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt
index 194d02cc25..33cf472e18 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt
@@ -71,12 +71,14 @@ public class SpacingAroundParensRule : StandardRule("paren-spacing") {
nextLeaf!!.treeParent.removeChild(nextLeaf)
}
}
+
spacingBefore -> {
emit(prevLeaf!!.startOffset, "Unexpected spacing before \"${node.text}\"", true)
if (autoCorrect) {
prevLeaf.treeParent.removeChild(prevLeaf)
}
}
+
spacingAfter -> {
emit(node.startOffset + 1, "Unexpected spacing after \"${node.text}\"", true)
if (autoCorrect) {
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRangeOperatorRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRangeOperatorRule.kt
index bcd25ee1e4..d4293263e5 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRangeOperatorRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRangeOperatorRule.kt
@@ -30,12 +30,14 @@ public class SpacingAroundRangeOperatorRule : StandardRule("range-spacing") {
nextLeaf.node.treeParent.removeChild(nextLeaf.node)
}
}
+
prevLeaf is PsiWhiteSpace -> {
emit(prevLeaf.node.startOffset, "Unexpected spacing before \"${node.elementTypeDescription()}\"", true)
if (autoCorrect) {
prevLeaf.node.treeParent.removeChild(prevLeaf.node)
}
}
+
nextLeaf is PsiWhiteSpace -> {
emit(nextLeaf.node.startOffset, "Unexpected spacing after \"${node.elementTypeDescription()}\"", true)
if (autoCorrect) {
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSquareBracketsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSquareBracketsRule.kt
index 08ff2a9c88..81bc8f8fb2 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSquareBracketsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSquareBracketsRule.kt
@@ -38,6 +38,7 @@ public class SpacingAroundSquareBracketsRule : StandardRule("square-brackets-spa
// fun foo() {}
false
}
+
COLLECTION_LITERAL_EXPRESSION -> {
// Allow:
// @Foo(
@@ -49,6 +50,7 @@ public class SpacingAroundSquareBracketsRule : StandardRule("square-brackets-spa
// @Foo(fooBar = ["foo", "bar" ])
node.elementType == RBRACKET && prevLeaf.isWhiteSpaceWithoutNewline()
}
+
else -> {
prevLeaf.isWhiteSpaceWithoutNewline()
}
@@ -76,12 +78,14 @@ public class SpacingAroundSquareBracketsRule : StandardRule("square-brackets-spa
nextLeaf!!.treeParent.removeChild(nextLeaf)
}
}
+
spacingBefore -> {
emit(prevLeaf!!.startOffset, "Unexpected spacing before '${node.text}'", true)
if (autoCorrect) {
prevLeaf.treeParent.removeChild(prevLeaf)
}
}
+
spacingAfter -> {
emit(node.startOffset + 1, "Unexpected spacing after '${node.text}'", true)
if (autoCorrect) {
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt
index 3eb12370f7..6d46186d12 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt
@@ -78,6 +78,7 @@ public class StringTemplateIndentRule :
wrongIndentChar = "\t"
wrongIndentDescription = "tab"
}
+
IndentConfig.IndentStyle.TAB -> {
wrongIndentChar = " "
wrongIndentDescription = "space"
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt
index 1a91d539e6..85e8b090f6 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt
@@ -168,6 +168,7 @@ public class TrailingCommaOnCallSiteRule :
this.removeChild(trailingCommaNode)
}
}
+
TrailingCommaState.MISSING ->
if (isTrailingCommaAllowed) {
val prevNode = inspectNode.prevCodeLeaf()!!
@@ -185,6 +186,7 @@ public class TrailingCommaOnCallSiteRule :
}
}
}
+
TrailingCommaState.REDUNDANT -> {
emit(
trailingCommaNode!!.startOffset,
@@ -195,6 +197,7 @@ public class TrailingCommaOnCallSiteRule :
this.removeChild(trailingCommaNode)
}
}
+
TrailingCommaState.NOT_EXISTS -> Unit
}
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt
index 59106a484d..cf07130557 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt
@@ -302,6 +302,7 @@ public class TrailingCommaOnDeclarationSiteRule :
this.removeChild(trailingCommaNode)
}
}
+
TrailingCommaState.MISSING ->
if (isTrailingCommaAllowed) {
val leafBeforeArrowOrNull = leafBeforeArrowOrNull()
@@ -361,6 +362,7 @@ public class TrailingCommaOnDeclarationSiteRule :
}
}
}
+
TrailingCommaState.REDUNDANT -> {
emit(
trailingCommaNode!!.startOffset,
@@ -371,6 +373,7 @@ public class TrailingCommaOnDeclarationSiteRule :
this.removeChild(trailingCommaNode)
}
}
+
TrailingCommaState.NOT_EXISTS -> Unit
}
}
@@ -380,15 +383,19 @@ public class TrailingCommaOnDeclarationSiteRule :
element.parent is KtFunctionLiteral -> {
isMultiline(element.parent)
}
+
element is KtFunctionLiteral -> {
containsLineBreakInLeavesRange(element.valueParameterList!!, element.arrow!!)
}
+
element is KtWhenEntry -> {
containsLineBreakInLeavesRange(element.firstChild, element.arrow!!)
}
+
element is KtDestructuringDeclaration -> {
containsLineBreakInLeavesRange(element.lPar!!, element.rPar!!)
}
+
element is KtValueArgumentList &&
element.children.size == 1 &&
element.anyDescendantOfType() -> {
@@ -399,9 +406,11 @@ public class TrailingCommaOnDeclarationSiteRule :
val lastChild = element.collectDescendantsOfType().last()
containsLineBreakInLeavesRange(lastChild.rightBracket!!, element.rightParenthesis!!)
}
+
element is KtParameterList && element.parameters.isEmpty() -> {
false
}
+
else -> {
element.textContains('\n')
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySpacingRule.kt
index bee881f08d..13938a6547 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySpacingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySpacingRule.kt
@@ -64,6 +64,7 @@ public class TryCatchFinallySpacingRule :
BLOCK -> {
visitBlock(node, emit, autoCorrect)
}
+
CATCH, FINALLY -> {
visitClause(node, emit, autoCorrect)
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRule.kt
index 9848ea1197..3f9c2dbf57 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRule.kt
@@ -56,6 +56,7 @@ public class TypeArgumentListSpacingRule :
visitFunctionDeclaration(node, autoCorrect, emit)
visitInsideTypeArgumentList(node, autoCorrect, emit)
}
+
ElementType.SUPER_TYPE_LIST, ElementType.SUPER_EXPRESSION ->
visitInsideTypeArgumentList(node, autoCorrect, emit)
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRule.kt
index abad5abd0b..250182acd2 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRule.kt
@@ -270,6 +270,7 @@ public class TypeParameterListSpacingRule :
) {
when {
node.text == " " -> Unit
+
node.textContains('\n') -> {
emit(
node.startOffset,
@@ -280,6 +281,7 @@ public class TypeParameterListSpacingRule :
(node as LeafPsiElement).rawReplaceWithText(" ")
}
}
+
else -> {
emit(
node.startOffset,
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditionsTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditionsTest.kt
new file mode 100644
index 0000000000..828fca3d87
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditionsTest.kt
@@ -0,0 +1,247 @@
+package com.pinterest.ktlint.ruleset.standard.rules
+
+import com.pinterest.ktlint.ruleset.standard.rules.BlankLineBetweenWhenConditions.Companion.LINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY
+import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule
+import com.pinterest.ktlint.test.LintViolation
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+
+class BlankLineBetweenWhenConditionsTest {
+ private val blankLineAfterWhenConditionRuleAssertThat = assertThatRule { BlankLineBetweenWhenConditions() }
+
+ @Test
+ fun `Given a when-statement with single line when-conditions only then do no reformat`() {
+ val code =
+ """
+ val foo =
+ when (bar) {
+ BAR1 -> "bar1"
+ BAR2 -> "bar2"
+ else -> null
+ }
+ """.trimIndent()
+ blankLineAfterWhenConditionRuleAssertThat(code).hasNoLintViolations()
+ }
+
+ @Test
+ fun `Given a when-statement with single line when-conditions only which are separated by a blank line then remove the blank lines`() {
+ val code =
+ """
+ val foo =
+ when (bar) {
+ BAR1 -> "bar1"
+
+ BAR2 -> "bar2"
+
+ else -> null
+ }
+ """.trimIndent()
+ val formattedCode =
+ """
+ val foo =
+ when (bar) {
+ BAR1 -> "bar1"
+ BAR2 -> "bar2"
+ else -> null
+ }
+ """.trimIndent()
+ @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length")
+ blankLineAfterWhenConditionRuleAssertThat(code)
+ .hasLintViolations(
+ LintViolation(4, 1, "Unexpected blank lines between when-condition if all when-conditions are single lines"),
+ LintViolation(6, 1, "Unexpected blank lines between when-condition if all when-conditions are single lines"),
+ ).isFormattedAs(formattedCode)
+ }
+
+ @Nested
+ inner class `Given when statement with multiline when-condition` {
+ @Nested
+ inner class `Given linebreaks have to be added after when-condition` {
+ @Test
+ fun `Given a when-statement with a single line when-condition after a multiline when-condition then add a blank line between the when-conditions`() {
+ val code =
+ """
+ val foo =
+ when (bar) {
+ BAR1 -> "bar1"
+ BAR2 ->
+ "bar2"
+ else -> null
+ }
+ """.trimIndent()
+ val formattedCode =
+ """
+ val foo =
+ when (bar) {
+ BAR1 -> "bar1"
+
+ BAR2 ->
+ "bar2"
+
+ else -> null
+ }
+ """.trimIndent()
+ @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length")
+ blankLineAfterWhenConditionRuleAssertThat(code)
+ .hasLintViolations(
+ LintViolation(4, 1, "Add a blank line between all when-condition in case at least one multiline when-condition is found in the statement"),
+ LintViolation(6, 1, "Add a blank line between all when-condition in case at least one multiline when-condition is found in the statement"),
+ ).isFormattedAs(formattedCode)
+ }
+
+ @Test
+ fun `Given a when-statement with a single line when-condition before a multiline when-condition then add a blank line between the when-conditions`() {
+ val code =
+ """
+ val foo =
+ when (bar) {
+ BAR1 -> "bar1"
+ BAR2 -> "bar2"
+ else ->
+ null
+ }
+ """.trimIndent()
+ val formattedCode =
+ """
+ val foo =
+ when (bar) {
+ BAR1 -> "bar1"
+
+ BAR2 -> "bar2"
+
+ else ->
+ null
+ }
+ """.trimIndent()
+ @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length")
+ blankLineAfterWhenConditionRuleAssertThat(code)
+ .hasLintViolations(
+ LintViolation(4, 1, "Add a blank line between all when-condition in case at least one multiline when-condition is found in the statement"),
+ LintViolation(5, 1, "Add a blank line between all when-condition in case at least one multiline when-condition is found in the statement"),
+ ).isFormattedAs(formattedCode)
+ }
+
+ @Test
+ fun `Given xx a when-condition preceded by a comment and the when-condition needs to be preceded by blank line then add this line before the comment`() {
+ val code =
+ """
+ val foo =
+ when (bar) {
+ BAR1 ->
+ "bar1"
+ BAR2 ->
+ "bar2"
+ else ->
+ null
+ }
+ """.trimIndent()
+ val formattedCode =
+ """
+ val foo =
+ when (bar) {
+ BAR1 ->
+ "bar1"
+
+ BAR2 ->
+ "bar2"
+
+ else ->
+ null
+ }
+ """.trimIndent()
+ @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length")
+ blankLineAfterWhenConditionRuleAssertThat(code)
+ .hasLintViolations(
+ LintViolation(5, 1, "Add a blank line between all when-condition in case at least one multiline when-condition is found in the statement"),
+ LintViolation(7, 1, "Add a blank line between all when-condition in case at least one multiline when-condition is found in the statement"),
+ ).isFormattedAs(formattedCode)
+ }
+
+ @Test
+ fun `Given a when-condition preceded by a comment and the when-condition needs to be preceded by blank line then add this line before the comment`() {
+ val code =
+ """
+ val foo =
+ when (bar) {
+ BAR -> "bar"
+ // Some comment
+ else ->
+ null
+ }
+ """.trimIndent()
+ val formattedCode =
+ """
+ val foo =
+ when (bar) {
+ BAR -> "bar"
+
+ // Some comment
+ else ->
+ null
+ }
+ """.trimIndent()
+ @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length")
+ blankLineAfterWhenConditionRuleAssertThat(code)
+ .hasLintViolation(4, 1, "Add a blank line between all when-condition in case at least one multiline when-condition is found in the statement")
+ .isFormattedAs(formattedCode)
+ }
+ }
+
+ @Nested
+ inner class `Given no linebreaks may be added after when-condition` {
+ @Test
+ fun `Given a when-statement with a single line when-condition after a multiline when-condition then add a blank line between the when-conditions`() {
+ val code =
+ """
+ val foo =
+ when (bar) {
+ BAR1 -> "bar1"
+ BAR2 ->
+ "bar2"
+ else -> null
+ }
+ """.trimIndent()
+ @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length")
+ blankLineAfterWhenConditionRuleAssertThat(code)
+ .withEditorConfigOverride(LINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY to false)
+ .hasNoLintViolations()
+ }
+
+ @Test
+ fun `Given a when-statement with a single line when-condition before a multiline when-condition then add a blank line between the when-conditions`() {
+ val code =
+ """
+ val foo =
+ when (bar) {
+ BAR1 -> "bar1"
+ BAR2 -> "bar2"
+ else ->
+ null
+ }
+ """.trimIndent()
+ @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length")
+ blankLineAfterWhenConditionRuleAssertThat(code)
+ .withEditorConfigOverride(LINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY to false)
+ .hasNoLintViolations()
+ }
+
+ @Test
+ fun `Given a when-condition preceded by a comment and the when-condition needs to be preceded by blank line then add this line before the comment`() {
+ val code =
+ """
+ val foo =
+ when (bar) {
+ BAR -> "bar"
+ // Some comment
+ else ->
+ null
+ }
+ """.trimIndent()
+ @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length")
+ blankLineAfterWhenConditionRuleAssertThat(code)
+ .withEditorConfigOverride(LINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY to false)
+ .hasNoLintViolations()
+ }
+ }
+ }
+}