From 50e44a7d11207a96577fedd059cb95ffa225aa17 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Mon, 14 Oct 2024 17:33:40 +0200 Subject: [PATCH] Add new rule `when-entry-bracing` (#2829) Enforce consistent usage of braces in when entries. In case a when statement contains at least one when entry which uses braces around the body of the entry, then all when entries should use braces. Braces are helpful for following reasons: - Bodies of the when-conditions are all aligned at same column position - Closing braces helps in separating the when-conditions This rule is not incorporated in the Kotlin Coding conventions, nor in the Android Kotlin Styleguide. It is based on similar behavior in enforcing consistent use of braces in if-else statements. As of that the rule is only enabled automatically for code style `ktlint_official`. It can be enabled explicitly for other code styles. Closes #2557 --- .../snapshot/docs/rules/experimental.md | 73 +++++++ .../cli/reporter/format/FormatReporter.kt | 12 +- .../ktlint/cli/internal/FileUtils.kt | 11 +- .../ktlint/cli/internal/KtlintCommandLine.kt | 14 +- .../ktlint/cli/CommandLineTestRunner.kt | 8 +- .../rule/engine/core/api/IndentConfig.kt | 4 +- .../core/api/editorconfig/EditorConfig.kt | 6 +- .../MaxLineLengthEditorConfigProperty.kt | 7 +- .../engine/api/KtlintRuleEngineSuppression.kt | 7 +- .../rule/engine/internal/CodeFormatter.kt | 9 +- .../rule/engine/internal/KtlintSuppression.kt | 18 +- .../engine/internal/PositionInTextLocator.kt | 4 +- .../engine/internal/SuppressionLocator.kt | 25 ++- .../rulefilter/RuleExecutionRuleFilter.kt | 12 +- .../internal/rules/KtlintSuppressionRule.kt | 9 +- .../ktlint/rule/engine/api/KtLintTest.kt | 3 +- .../api/ktlint-ruleset-standard.api | 10 + .../standard/StandardRuleSetProvider.kt | 2 + .../ruleset/standard/rules/AnnotationRule.kt | 9 +- .../rules/BlankLineBeforeDeclarationRule.kt | 3 +- .../rules/ChainMethodContinuationRule.kt | 8 +- .../standard/rules/ChainWrappingRule.kt | 8 +- .../rules/ContextReceiverWrappingRule.kt | 6 +- .../standard/rules/FunctionNamingRule.kt | 4 +- .../standard/rules/FunctionSignatureRule.kt | 4 +- .../standard/rules/ImportOrderingRule.kt | 9 +- .../ruleset/standard/rules/IndentationRule.kt | 101 ++++++--- .../rules/MultilineExpressionWrappingRule.kt | 4 +- .../rules/NoConsecutiveCommentsRule.kt | 12 +- .../standard/rules/NoSemicolonsRule.kt | 8 +- .../standard/rules/NoTrailingSpacesRule.kt | 7 +- .../standard/rules/NoUnusedImportsRule.kt | 4 +- .../standard/rules/NoWildcardImportsRule.kt | 23 +- .../rules/ParameterListWrappingRule.kt | 34 +-- .../standard/rules/SpacingAroundColonRule.kt | 11 +- .../rules/SpacingAroundDoubleColonRule.kt | 12 +- .../standard/rules/SpacingAroundParensRule.kt | 8 +- .../standard/rules/StatementWrappingRule.kt | 9 +- .../rules/TrailingCommaOnCallSiteRule.kt | 10 +- .../TrailingCommaOnDeclarationSiteRule.kt | 28 ++- .../rules/TypeArgumentListSpacingRule.kt | 3 +- .../rules/TypeParameterListSpacingRule.kt | 4 +- .../standard/rules/WhenEntryBracing.kt | 169 +++++++++++++++ .../standard/rules/WhenEntryBracingTest.kt | 198 ++++++++++++++++++ 44 files changed, 773 insertions(+), 157 deletions(-) create mode 100644 ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WhenEntryBracing.kt create mode 100644 ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WhenEntryBracingTest.kt diff --git a/documentation/snapshot/docs/rules/experimental.md b/documentation/snapshot/docs/rules/experimental.md index 5c13e752a3..fffb10205d 100644 --- a/documentation/snapshot/docs/rules/experimental.md +++ b/documentation/snapshot/docs/rules/experimental.md @@ -285,3 +285,76 @@ Suppress or disable rule (1) ktlint_standard_square-brackets-spacing = disabled ``` +## When-entry bracing + +Enforce consistent usages of braces inside the when-statement. All when-entries in the when-statement should use braces around their bodies in case at least one when-entry has a multiline body, or when the body is surrounded by braces. + +Braces are helpful for following reasons: + +- Bodies of the when-conditions are all aligned at same column position +- Closing braces helps in separating the when-conditions + +This rule is not incorporated in the Kotlin Coding conventions, nor in the Android Kotlin Styleguide. It is based on similar behavior in enforcing consistent use of braces in if-else statements. As of that the rule is only enabled automatically for code style `ktlint_official`. It can be enabled explicitly for other code styles. + +=== "[:material-heart:](#) Ktlint" + + ```kotlin + val foo1 = + when (bar) { + BAR1 -> "bar1" + BAR2 -> "bar2" + else -> null + } + + val foo2 = + when (bar) { + BAR1 -> { + "bar1" + } + BAR2 -> { + "bar2" + } + else -> { + null + } + } + ``` + +=== "[:material-heart-off-outline:](#) Disallowed" + + ```kotlin + val foo3 = + when (bar) { + BAR1 -> "bar1" + BAR2 -> { + "bar2" + } + else -> null + } + + val foo4 = + when (bar) { + BAR1 -> "bar1" + BAR2 -> + "bar2" + else -> null + } + ``` + +Rule id: `standard:when-entry-spacing` + +Suppress or disable rule (1) +{ .annotate } + +1. Suppress rule in code with annotation below: + ```kotlin + @Suppress("ktlint:standard:when-entry-spacing") + ``` + Enable rule via `.editorconfig` + ```editorconfig + ktlint_standard_when-entry-spacing = enabled + ``` + Disable rule via `.editorconfig` + ```editorconfig + ktlint_standard_when-entry-spacing = disabled + ``` 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 62e979f297..7e29eaf880 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 @@ -41,29 +41,33 @@ public class FormatReporter( val canNotBeAutocorrected = countCanNotBeAutoCorrected.getOrDefault(file, 0) val result = when { - canNotBeAutocorrected == 1 -> + canNotBeAutocorrected == 1 -> { if (format) { "Format not completed (1 violation needs manual fixing)" } else { "Format required (1 violation needs manual fixing)" } + } - canNotBeAutocorrected > 1 -> + 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 -> + countAutoCorrectPossibleOrDone.getOrDefault(file, 0) > 0 -> { if (format) { "Format completed (all violations have been fixed)" } else { "Format required (all violations can be autocorrected)" } + } - else -> + else -> { "Format not needed (no violations found)" + } } out.println( "${colorFileName(file)}${":".colored()} $result", 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 5cef6b12aa..fa292897b9 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 @@ -158,14 +158,9 @@ internal fun FileSystem.fileSequence( private fun Path.findCommonParentDir(path: Path): Path = when { - path.startsWith(this) -> - this - - startsWith(path) -> - path - - else -> - this@findCommonParentDir.findCommonParentDir(path.parent) + path.startsWith(this) -> this + startsWith(path) -> path + else -> this@findCommonParentDir.findCommonParentDir(path.parent) } private fun FileSystem.expand( 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 4235076011..b63b2a40f0 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 @@ -526,13 +526,16 @@ internal class KtlintCommandLine : CliktCommand(name = "ktlint") { } } when { - code.isStdIn -> print(formattedFileContent) + code.isStdIn -> { + print(formattedFileContent) + } - code.content != formattedFileContent -> + code.content != formattedFileContent -> { code .filePath ?.toFile() ?.writeText(formattedFileContent, charset("UTF-8")) + } } } } catch (e: Exception) { @@ -637,7 +640,7 @@ internal class KtlintCommandLine : CliktCommand(name = "ktlint") { private fun Exception.toKtlintCliError(code: Code): KtlintCliError = this.let { e -> when (e) { - is KtLintParseException -> + is KtLintParseException -> { KtlintCliError( line = e.line, col = e.col, @@ -645,6 +648,7 @@ internal class KtlintCommandLine : CliktCommand(name = "ktlint") { 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}" } @@ -661,7 +665,9 @@ internal class KtlintCommandLine : CliktCommand(name = "ktlint") { ) } - else -> throw e + 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 8841bba05e..15ccdc88ce 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 @@ -128,7 +128,9 @@ class CommandLineTestRunner( arrayOf(comSpec, "/C") } - else -> arrayOf("/bin/sh", "-c") + else -> { + arrayOf("/bin/sh", "-c") + } } private fun ktlintCommand(arguments: List): String = @@ -189,7 +191,9 @@ class CommandLineTestRunner( } ?: PATH } - else -> 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 385d136ff9..22445ab744 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 @@ -111,7 +111,9 @@ public class IndentConfig( val indent = getTextAfterLastNewLine(text) require(indent.matches(TABS_AND_SPACES)) return when (indentStyle) { - SPACE -> indent.replaceTabWithSpaces() + SPACE -> { + indent.replaceTabWithSpaces() + } TAB -> { "\t".repeat(indentLevelFrom(indent)) diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig.kt index cf17c21c34..ff35c0a365 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig.kt @@ -38,13 +38,15 @@ public class EditorConfig( */ public operator fun get(editorConfigProperty: EditorConfigProperty): T { when { - editorConfigProperty.deprecationError != null -> + editorConfigProperty.deprecationError != null -> { throw DeprecatedEditorConfigPropertyException( "Property '${editorConfigProperty.name}' is disallowed: ${editorConfigProperty.deprecationError}", ) + } - editorConfigProperty.deprecationWarning != null -> + editorConfigProperty.deprecationWarning != null -> { LOGGER.warn { "Property '${editorConfigProperty.name}' is deprecated: ${editorConfigProperty.deprecationWarning}" } + } } val property = properties.getOrElse(editorConfigProperty.name) { diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/MaxLineLengthEditorConfigProperty.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/MaxLineLengthEditorConfigProperty.kt index 59c6890118..4a221a5b3b 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/MaxLineLengthEditorConfigProperty.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/MaxLineLengthEditorConfigProperty.kt @@ -34,9 +34,11 @@ public val MAX_LINE_LENGTH_PROPERTY: EditorConfigProperty = * Internally, Ktlint uses integer 'Int.MAX_VALUE' to indicate that the max line length has to be ignored as this is easier * in comparisons to check whether the maximum length of a line is exceeded. */ - property.sourceValue == MAX_LINE_LENGTH_PROPERTY_OFF_EDITOR_CONFIG -> MAX_LINE_LENGTH_PROPERTY_OFF + property.sourceValue == MAX_LINE_LENGTH_PROPERTY_OFF_EDITOR_CONFIG -> { + MAX_LINE_LENGTH_PROPERTY_OFF + } - else -> + else -> { PropertyType .max_line_length .parse(property.sourceValue) @@ -55,6 +57,7 @@ public val MAX_LINE_LENGTH_PROPERTY: EditorConfigProperty = it.parsed } } + } } }, propertyWriter = { property -> 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 8ba6536919..321e77e133 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 @@ -35,9 +35,11 @@ public fun KtLintRuleEngine.insertSuppression( private fun ASTNode.findLeafElementAt(suppression: KtlintSuppression): ASTNode = when (suppression) { - is KtlintSuppressionForFile -> this + is KtlintSuppressionForFile -> { + this + } - is KtlintSuppressionAtOffset -> + is KtlintSuppressionAtOffset -> { findLeafElementAt(suppression.offsetFromStartOf(text)) ?.let { if (it.isWhiteSpace()) { @@ -48,6 +50,7 @@ private fun ASTNode.findLeafElementAt(suppression: KtlintSuppression): ASTNode = } } ?: throw KtlintSuppressionNoElementFoundException(suppression) + } } private fun KtlintSuppressionAtOffset.offsetFromStartOf(code: String): Int { diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index d9df1f4919..b3cb713e49 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -53,7 +53,7 @@ internal class CodeFormatter( var codeContent = formattedCode(lineSeparator) val errors = mutableSetOf>() var formatRunCount = 0 - var mutated: Boolean = false + var mutated = false do { val newErrors = format(autocorrectHandler, code) errors.addAll(newErrors) @@ -168,10 +168,13 @@ internal class CodeFormatter( when { eolEditorConfigProperty == PropertyType.EndOfLineValue.crlf || eolEditorConfigProperty != PropertyType.EndOfLineValue.lf && - doesNotContain('\r') -> + doesNotContain('\r') -> { "\r\n".also { LOGGER.trace { "line separator: ${eolEditorConfigProperty.name} --> CRLF" } } + } - else -> "\n".also { LOGGER.trace { "line separator: ${eolEditorConfigProperty.name} --> LF" } } + else -> { + "\n".also { LOGGER.trace { "line separator: ${eolEditorConfigProperty.name} --> LF" } } + } } private fun Code.doesNotContain(char: Char) = content.lastIndexOf(char) != -1 diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppression.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppression.kt index a7a9f1c1fc..0db38f466e 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppression.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppression.kt @@ -81,19 +81,23 @@ internal fun ASTNode.insertKtlintRuleSuppression( // - otherwise to the @SuppressWarnings annotation if found // - otherwise create a new @Suppress annotation when { - suppressionAnnotations.containsKey(SuppressAnnotationType.SUPPRESS) -> + suppressionAnnotations.containsKey(SuppressAnnotationType.SUPPRESS) -> { fullyQualifiedSuppressionIds.mergeInto( suppressionAnnotations.getValue(SuppressAnnotationType.SUPPRESS), SuppressAnnotationType.SUPPRESS, ) + } - suppressionAnnotations.containsKey(SuppressAnnotationType.SUPPRESS_WARNINGS) -> + suppressionAnnotations.containsKey(SuppressAnnotationType.SUPPRESS_WARNINGS) -> { fullyQualifiedSuppressionIds.mergeInto( suppressionAnnotations.getValue(SuppressAnnotationType.SUPPRESS_WARNINGS), SuppressAnnotationType.SUPPRESS_WARNINGS, ) + } - else -> targetASTNode.createSuppressAnnotation(SuppressAnnotationType.SUPPRESS, fullyQualifiedSuppressionIds) + else -> { + targetASTNode.createSuppressAnnotation(SuppressAnnotationType.SUPPRESS, fullyQualifiedSuppressionIds) + } } } @@ -400,9 +404,13 @@ internal fun String.isKtlintSuppressionId() = removePrefix(DOUBLE_QUOTE).startsW internal fun String.toFullyQualifiedKtlintSuppressionId(): String = when (this) { - KTLINT_SUPPRESSION_ID_ALL_RULES -> this + KTLINT_SUPPRESSION_ID_ALL_RULES -> { + this + } - KTLINT_PREFIX -> this.surroundWith(DOUBLE_QUOTE) + KTLINT_PREFIX -> { + this.surroundWith(DOUBLE_QUOTE) + } else -> { removeSurrounding(DOUBLE_QUOTE) 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 87e730930e..2685b97ff5 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 @@ -58,7 +58,9 @@ private class SegmentTree( r: Int, ): Int = when { - l > r -> -1 + l > r -> { + -1 + } else -> { val i = l + (r - l) / 2 diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocator.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocator.kt index 06be443451..d0f0433555 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocator.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocator.kt @@ -59,15 +59,17 @@ internal class SuppressionLocator( val commentSuppressionsHints = mutableListOf() rootNode.findSuppressionHints { node -> when (val psi = node.psi) { - is PsiComment -> + is PsiComment -> { node .createSuppressionHintFromComment() ?.let { commentSuppressionsHints.add(it) } + } - is KtAnnotated -> + is KtAnnotated -> { psi .createSuppressionHintFromAnnotations() ?.let { suppressionHints.add(it) } + } } } @@ -93,22 +95,25 @@ internal class SuppressionLocator( .takeIf { it.isNotEmpty() } ?.let { parts -> when (parts[0]) { - formatterTags.formatterTagOff -> + formatterTags.formatterTagOff -> { CommentSuppressionHint( this, HashSet(parts.tail()), BLOCK_START, ) + } - formatterTags.formatterTagOn -> + formatterTags.formatterTagOn -> { CommentSuppressionHint( this, HashSet(parts.tail()), BLOCK_END, ) + } - else -> + else -> { null + } } } @@ -184,19 +189,23 @@ internal class SuppressionLocator( .flatMap { it.findRuleSuppressionIds() } .let { suppressedRuleIds -> when { - suppressedRuleIds.isEmpty() -> null + suppressedRuleIds.isEmpty() -> { + null + } - suppressedRuleIds.contains(ALL_KTLINT_RULES_SUPPRESSION_ID) -> + suppressedRuleIds.contains(ALL_KTLINT_RULES_SUPPRESSION_ID) -> { SuppressionHint( IntRange(startOffset, endOffset - 1), emptySet(), ) + } - else -> + else -> { SuppressionHint( IntRange(startOffset, endOffset - 1), suppressedRuleIds.toSet(), ) + } } } 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 f79080f500..acccda6e53 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 @@ -94,17 +94,21 @@ private class RuleExecutionFilter( private fun isRuleConditionallyEnabled(rule: Rule) = when { - rule is Rule.Experimental && rule is Rule.OfficialCodeStyle -> + rule is Rule.Experimental && rule is Rule.OfficialCodeStyle -> { isExperimentalEnabled(rule) && isOfficialCodeStyleEnabled(rule) + } - rule is Rule.Experimental -> + rule is Rule.Experimental -> { isExperimentalEnabled(rule) + } - rule is Rule.OfficialCodeStyle -> + rule is Rule.OfficialCodeStyle -> { isOfficialCodeStyleEnabled(rule) + } - else -> + else -> { isRuleSetEnabled(rule) + } } private fun isExperimentalEnabled(rule: Rule) = diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRule.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRule.kt index 43ec288cb8..eba445d21c 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRule.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRule.kt @@ -352,19 +352,22 @@ private class KtLintDirective( private fun ASTNode.ktlintDirectiveOrNull(ruleIdValidator: (String) -> Boolean): KtLintDirective? { val ktlintDirectiveString = when (elementType) { - EOL_COMMENT -> + EOL_COMMENT -> { text .removePrefix("//") .trim() + } - BLOCK_COMMENT -> + BLOCK_COMMENT -> { text .removePrefix("/*") .removeSuffix("*/") .trim() + } - else -> + else -> { return null + } } val ktlintDirectiveType = ktlintDirectiveString.toKtlintDirectiveTypeOrNull() 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 9ab4a9dae7..2bf0b4a3ad 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 @@ -527,8 +527,9 @@ private class AutoCorrectErrorRule : } } - STRING_VALUE_NOT_TO_BE_CORRECTED -> + 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 d23b5980fe..6e9f14acbe 100644 --- a/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api +++ b/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api @@ -987,6 +987,16 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCom public static final fun getVALUE_PARAMETER_COMMENT_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; } +public final class com/pinterest/ktlint/ruleset/standard/rules/WhenEntryBracing : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental, com/pinterest/ktlint/rule/engine/core/api/Rule$OfficialCodeStyle, com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler { + 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;Lkotlin/jvm/functions/Function3;)V +} + +public final class com/pinterest/ktlint/ruleset/standard/rules/WhenEntryBracingKt { + public static final fun getWHEN_ENTRY_BRACING_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; +} + public final class com/pinterest/ktlint/ruleset/standard/rules/WrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/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 26a6b41dfd..d0db40ecd3 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 @@ -99,6 +99,7 @@ import com.pinterest.ktlint.ruleset.standard.rules.TypeParameterListSpacingRule import com.pinterest.ktlint.ruleset.standard.rules.UnnecessaryParenthesesBeforeTrailingLambdaRule import com.pinterest.ktlint.ruleset.standard.rules.ValueArgumentCommentRule import com.pinterest.ktlint.ruleset.standard.rules.ValueParameterCommentRule +import com.pinterest.ktlint.ruleset.standard.rules.WhenEntryBracing import com.pinterest.ktlint.ruleset.standard.rules.WrappingRule public class StandardRuleSetProvider : RuleSetProviderV3(RuleSetId.STANDARD) { @@ -200,6 +201,7 @@ public class StandardRuleSetProvider : RuleSetProviderV3(RuleSetId.STANDARD) { RuleProvider { ValueArgumentCommentRule() }, RuleProvider { ValueParameterCommentRule() }, RuleProvider { UnnecessaryParenthesesBeforeTrailingLambdaRule() }, + RuleProvider { WhenEntryBracing() }, RuleProvider { WrappingRule() }, ) } 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 2ce641b009..fcb611475c 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 @@ -149,14 +149,17 @@ public class AnnotationRule : if (node.shouldWrapAnnotations()) { val expectedIndent = when { - node.elementType == ANNOTATED_EXPRESSION -> + node.elementType == ANNOTATED_EXPRESSION -> { indentConfig.siblingIndentOf(node) + } - node.hasAnnotationBeforeConstructor() -> + node.hasAnnotationBeforeConstructor() -> { indentConfig.siblingIndentOf(node.treeParent) + } - else -> + else -> { indentConfig.parentIndentOf(node) + } } node diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRule.kt index 53365e5b2f..29a28e18cb 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRule.kt @@ -56,8 +56,9 @@ public class BlankLineBeforeDeclarationRule : OBJECT_DECLARATION, PROPERTY, PROPERTY_ACCESSOR, - -> + -> { visitDeclaration(node, emit) + } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt index 299ce796b9..80b2a9d010 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt @@ -207,7 +207,9 @@ public class ChainMethodContinuationRule : chainOperators.size >= forceMultilineWhenChainOperatorCountGreaterOrEqualThanProperty } - else -> false + else -> { + false + } } private fun ChainedExpression.isChainedExpressionOnStringTemplate() = @@ -425,7 +427,9 @@ public class ChainMethodContinuationRule : .singleOrNull() } - else -> null + else -> { + null + } } private fun ASTNode.createBaseChainedExpression(chainOperator: ASTNode): ChainedExpression { 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 cf0b7fbd75..ccea277ccf 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 @@ -140,9 +140,13 @@ public class ChainWrappingRule : prevLeaf } - nextLeaf.isWhiteSpaceWithoutNewline() -> nextLeaf + nextLeaf.isWhiteSpaceWithoutNewline() -> { + nextLeaf + } - else -> null + else -> { + null + } } if (node.treeParent.elementType == OPERATION_REFERENCE) { 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 1b14e576dd..5d6bdb91e3 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 @@ -66,11 +66,13 @@ public class ContextReceiverWrappingRule : emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when { - node.elementType == CONTEXT_RECEIVER_LIST -> + node.elementType == CONTEXT_RECEIVER_LIST -> { visitContextReceiverList(node, emit) + } - node.elementType == TYPE_ARGUMENT_LIST && node.isPartOf(CONTEXT_RECEIVER) -> + node.elementType == TYPE_ARGUMENT_LIST && node.isPartOf(CONTEXT_RECEIVER) -> { visitContextReceiverTypeArgumentList(node, 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 ea15c9ce25..44564b9f9e 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 @@ -159,7 +159,9 @@ public class FunctionNamingRule : it.annotationEntryName() in excludeWhenAnnotatedWith } - else -> false + else -> { + false + } } } ?: false diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt index b56b62e85f..52dde0537f 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt @@ -571,7 +571,9 @@ public class FunctionSignatureRule : ) } - else -> false + else -> { + false + } } if (mergeWithFunctionSignature) { emit( 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 667ce3f08f..ee939b239f 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 @@ -144,8 +144,9 @@ public class ImportOrderingRule : children.forEach { current -> when { - current.isWhiteSpace() && current.text.count { it == '\n' } > 1 -> + current.isWhiteSpace() && current.text.count { it == '\n' } > 1 -> { imports += current + } current.elementType == ElementType.IMPORT_DIRECTIVE -> { if (importTextSet.add(current.text)) { @@ -219,11 +220,12 @@ public class ImportOrderingRule : private val EDITOR_CONFIG_PROPERTY_PARSER: (String, String?) -> PropertyType.PropertyValue> = { _, value -> when { - value.isNullOrBlank() -> + value.isNullOrBlank() -> { PropertyType.PropertyValue.invalid( value, "Import layout must contain at least one entry of a wildcard symbol (*)", ) + } value == "idea" -> { LOGGER.warn { @@ -246,7 +248,7 @@ public class ImportOrderingRule : ) } - else -> + else -> { try { PropertyType.PropertyValue.valid( value, @@ -258,6 +260,7 @@ public class ImportOrderingRule : "Unexpected imports layout: $value", ) } + } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt index 73c6bed24d..cbba25f518 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt @@ -215,11 +215,12 @@ public class IndentationRule : node.elementType == CONTEXT_RECEIVER_LIST || node.elementType == LONG_STRING_TEMPLATE_ENTRY || node.elementType == STRING_TEMPLATE || - node.elementType == VALUE_ARGUMENT_LIST -> + node.elementType == VALUE_ARGUMENT_LIST -> { startIndentContext( fromAstNode = node, lastChildIndent = "", ) + } (node.elementType == SUPER_TYPE_LIST && !node.isPrecededByComment()) || (node.isPartOfComment() && node.nextCodeSibling()?.elementType == SUPER_TYPE_LIST) -> { @@ -249,13 +250,15 @@ public class IndentationRule : } } - node.elementType == VALUE_ARGUMENT -> + node.elementType == VALUE_ARGUMENT -> { visitValueArgument(node) + } - node.elementType == SECONDARY_CONSTRUCTOR -> + node.elementType == SECONDARY_CONSTRUCTOR -> { visitSecondaryConstructor(node) + } - node.elementType == PARENTHESIZED -> + node.elementType == PARENTHESIZED -> { if (codeStyle == ktlint_official) { // Contrary to the IntelliJ IDEA default formatter, do not indent the closing RPAR startIndentContext( @@ -265,9 +268,10 @@ public class IndentationRule : } else if (node.treeParent.treeParent.elementType != IF) { startIndentContext(node) } + } node.elementType == TYPE_ARGUMENT_LIST || - node.elementType == TYPE_PARAMETER_LIST -> + node.elementType == TYPE_PARAMETER_LIST -> { if (codeStyle == ktlint_official) { // Contrary to the IntelliJ IDEA default formatter, do not indent the closing angle bracket startIndentContext( @@ -277,55 +281,68 @@ public class IndentationRule : } else { startIndentContext(node) } + } node.elementType == BINARY_WITH_TYPE || - node.elementType == USER_TYPE -> + node.elementType == USER_TYPE -> { startIndentContext(node) + } node.elementType == IS_EXPRESSION || node.elementType == PREFIX_EXPRESSION || - node.elementType == POSTFIX_EXPRESSION -> + node.elementType == POSTFIX_EXPRESSION -> { startIndentContext(node) + } node.elementType == DELEGATED_SUPER_TYPE_ENTRY || node.elementType == ANNOTATED_EXPRESSION || - node.elementType == TYPE_REFERENCE -> + node.elementType == TYPE_REFERENCE -> { startIndentContext( fromAstNode = node, childIndent = "", ) + } - node.elementType == IF -> + node.elementType == IF -> { visitIf(node) + } - node.elementType == LBRACE -> + node.elementType == LBRACE -> { visitLbrace(node) + } node.elementType == VALUE_PARAMETER_LIST && - node.treeParent.elementType != FUNCTION_LITERAL -> + node.treeParent.elementType != FUNCTION_LITERAL -> { startIndentContext( fromAstNode = node, lastChildIndent = "", ) + } node.elementType == LPAR && - node.nextCodeSibling()?.elementType == CONDITION -> + node.nextCodeSibling()?.elementType == CONDITION -> { visitLparBeforeCondition(node) + } - node.elementType == VALUE_PARAMETER -> + node.elementType == VALUE_PARAMETER -> { visitValueParameter(node) + } - node.elementType == FUN -> + node.elementType == FUN -> { visitFun(node) + } - node.elementType == CLASS -> + node.elementType == CLASS -> { visitClass(node) + } - node.elementType == OBJECT_DECLARATION -> + node.elementType == OBJECT_DECLARATION -> { visitObjectDeclaration(node) + } - node.elementType == BINARY_EXPRESSION -> + node.elementType == BINARY_EXPRESSION -> { visitBinaryExpression(node) + } node.elementType in CHAINABLE_EXPRESSION -> { if (codeStyle == ktlint_official && @@ -352,45 +369,57 @@ public class IndentationRule : } node.elementType == IDENTIFIER && - node.treeParent.elementType == PROPERTY -> + node.treeParent.elementType == PROPERTY -> { visitIdentifierInProperty(node) + } node.elementType == LITERAL_STRING_TEMPLATE_ENTRY && - node.nextCodeSibling()?.elementType == CLOSING_QUOTE -> + node.nextCodeSibling()?.elementType == CLOSING_QUOTE -> { visitWhiteSpaceBeforeClosingQuote(node, emit) + } - node.elementType == WHEN -> + node.elementType == WHEN -> { visitWhen(node) + } - node.elementType == WHEN_ENTRY -> + node.elementType == WHEN_ENTRY -> { visitWhenEntry(node) + } node.elementType == WHERE_KEYWORD && - node.nextCodeSibling()?.elementType == TYPE_CONSTRAINT_LIST -> + node.nextCodeSibling()?.elementType == TYPE_CONSTRAINT_LIST -> { visitWhereKeywordBeforeTypeConstraintList(node) + } - node.elementType == KDOC -> + node.elementType == KDOC -> { visitKdoc(node) + } node.elementType == PROPERTY_ACCESSOR || - node.elementType == TYPEALIAS -> + node.elementType == TYPEALIAS -> { visitPropertyAccessor(node) + } node.elementType == FOR || - node.elementType == WHILE -> + node.elementType == WHILE -> { visitConditionalLoop(node) + } - node.elementType == LBRACKET -> + node.elementType == LBRACKET -> { visitLBracket(node) + } - node.elementType == NULLABLE_TYPE -> + node.elementType == NULLABLE_TYPE -> { visitNullableType(node) + } - node.elementType == DESTRUCTURING_DECLARATION -> + node.elementType == DESTRUCTURING_DECLARATION -> { visitDestructuringDeclaration(node) + } - node.elementType == TRY -> + node.elementType == TRY -> { visitTryCatchFinally(node) + } else -> { LOGGER.trace { "No processing for ${node.elementType}: ${node.textWithEscapedTabAndNewline()}" } @@ -1149,14 +1178,18 @@ public class IndentationRule : val adjustedChildIndent = when { this == lastIndexContext.fromASTNode.firstChildLeafOrSelf() || - nextLeaf == lastIndexContext.fromASTNode.firstChildLeafOrSelf() -> + nextLeaf == lastIndexContext.fromASTNode.firstChildLeafOrSelf() -> { lastIndexContext.firstChildIndent + } this == lastIndexContext.toASTNode || - nextLeaf == lastIndexContext.toASTNode -> + nextLeaf == lastIndexContext.toASTNode -> { lastIndexContext.lastChildIndent + } - else -> lastIndexContext.childIndent + else -> { + lastIndexContext.childIndent + } } return lastIndexContext.nodeIndent + adjustedChildIndent } @@ -1233,7 +1266,9 @@ public class IndentationRule : TYPE_CONSTRAINT_CONTINUATION_INDENT } - else -> "" + else -> { + "" + } } val nodeIndent = text.substringAfterLast("\n") return if (nodeIndent.endsWith(acceptableTrailingSpaces)) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingRule.kt index e7a72931cd..1088658bcc 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingRule.kt @@ -121,7 +121,9 @@ public class MultilineExpressionWrappingRule : .nextLeaf { !it.isWhiteSpaceWithoutNewline() && !it.isPartOfComment() } ?.takeIf { !it.isWhiteSpaceWithNewline() } when { - leafOnSameLineAfterMultilineExpression == null -> Unit + leafOnSameLineAfterMultilineExpression == null -> { + Unit + } leafOnSameLineAfterMultilineExpression.treeParent.elementType == OPERATION_REFERENCE -> { // When binary expressions are wrapped, each binary expression for itself is checked whether it is a 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 e3816dde27..d33eaaad5f 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 @@ -129,11 +129,13 @@ public class NoConsecutiveCommentsRule : EOL_COMMENT, BLOCK_COMMENT, KDOC_START, - -> + -> { true + } - else -> + else -> { false + } } private fun ASTNode?.isEndOfComment() = @@ -141,11 +143,13 @@ public class NoConsecutiveCommentsRule : EOL_COMMENT, BLOCK_COMMENT, KDOC_END, - -> + -> { true + } - else -> + else -> { false + } } private fun ASTNode.commentType() = diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt index a7cdca8f9b..d4ccd8b115 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt @@ -78,7 +78,9 @@ public class NoSemicolonsRule : private fun ASTNode?.doesNotRequirePreSemi() = when { - this == null -> true + this == null -> { + true + } this is PsiWhiteSpace -> { nextLeaf { @@ -94,7 +96,9 @@ public class NoSemicolonsRule : } } - else -> false + else -> { + false + } } private fun isNoSemicolonRequiredAfter(node: ASTNode): Boolean { 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 d0b209e09d..3d78892c69 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 @@ -45,10 +45,11 @@ public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") { .mapIndexed { index, line -> val modifiedLine = when { - node.elementType != EOL_COMMENT && index == lines.size - 1 && node.nextLeaf() != null -> + node.elementType != EOL_COMMENT && index == lines.size - 1 && node.nextLeaf() != null -> { // 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() @@ -60,7 +61,9 @@ public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") { modifiedLine } - else -> line + else -> { + line + } } violationOffset += line.length + 1 modifiedLine 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 4b3f280ff7..66cded850f 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 @@ -117,7 +117,9 @@ public class NoUnusedImportsRule : } } - BY_KEYWORD -> foundByKeyword = true + BY_KEYWORD -> { + foundByKeyword = true + } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt index d21a825530..7637801dfc 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt @@ -68,19 +68,16 @@ public class NoWildcardImportsRule : private val PACKAGES_TO_USE_ON_DEMAND_IMPORT_PROPERTY_PARSER: (String, String?) -> PropertyType.PropertyValue> = { _, value -> - when { - else -> - try { - PropertyType.PropertyValue.valid( - value, - value?.let(Companion::parseAllowedWildcardImports) ?: emptyList(), - ) - } catch (e: IllegalArgumentException) { - PropertyType.PropertyValue.invalid( - value, - "Unexpected imports layout: $value", - ) - } + try { + PropertyType.PropertyValue.valid( + value, + value?.let(Companion::parseAllowedWildcardImports) ?: emptyList(), + ) + } catch (e: IllegalArgumentException) { + PropertyType.PropertyValue.invalid( + value, + "Unexpected imports layout: $value", + ) } } 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 551d0c76c1..2232e6377e 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 @@ -128,20 +128,34 @@ public class ParameterListWrappingRule : private fun ASTNode.needToWrapParameterList() = when { - hasNoParameters() -> false + hasNoParameters() -> { + false + } - codeStyle != ktlint_official && isPartOfFunctionLiteralInNonKtlintOfficialCodeStyle() -> false + codeStyle != ktlint_official && isPartOfFunctionLiteralInNonKtlintOfficialCodeStyle() -> { + false + } - codeStyle == ktlint_official && containsAnnotatedParameter() -> true + codeStyle == ktlint_official && containsAnnotatedParameter() -> { + true + } - codeStyle == ktlint_official && isPartOfFunctionLiteralStartingOnSameLineAsClosingParenthesisOfPrecedingReferenceExpression() -> + codeStyle == ktlint_official && + isPartOfFunctionLiteralStartingOnSameLineAsClosingParenthesisOfPrecedingReferenceExpression() -> { false + } - textContains('\n') -> true + textContains('\n') -> { + true + } - isOnLineExceedingMaxLineLength() -> true + isOnLineExceedingMaxLineLength() -> { + true + } - else -> false + else -> { + false + } } private fun ASTNode.hasNoParameters(): Boolean { @@ -311,12 +325,8 @@ public class ParameterListWrappingRule : private fun errorMessage(node: ASTNode) = when (node.elementType) { LPAR -> """Unnecessary newline before "("""" - - VALUE_PARAMETER -> - "Parameter should start on a newline" - + 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/SpacingAroundColonRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt index 4e71d6dfff..66400cdc8b 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt @@ -187,7 +187,9 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { private inline val ASTNode.spacingBefore: Boolean get() = when { - psi.parent is KtClassOrObject -> true + psi.parent is KtClassOrObject -> { + true + } psi.parent is KtConstructor<*> -> { // constructor : this/super @@ -199,10 +201,13 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { true } - psi.parent.parent is KtTypeParameterList -> + psi.parent.parent is KtTypeParameterList -> { true + } - else -> false + else -> { + false + } } private inline val ASTNode.noSpacingBefore: Boolean 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 3281926526..692ff06ed7 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 @@ -30,18 +30,24 @@ public class SpacingAroundDoubleColonRule : StandardRule("double-colon-spacing") var removeSingleWhiteSpace = false val spacingBefore = when { - node.isPartOf(CLASS_LITERAL_EXPRESSION) && prevLeaf is PsiWhiteSpace -> true + node.isPartOf(CLASS_LITERAL_EXPRESSION) && prevLeaf is PsiWhiteSpace -> { + true + } // Clazz::class - node.isPartOf(CALLABLE_REFERENCE_EXPRESSION) && prevLeaf is PsiWhiteSpace -> // String::length, ::isOdd + node.isPartOf(CALLABLE_REFERENCE_EXPRESSION) && prevLeaf is PsiWhiteSpace -> { + // String::length, ::isOdd if (node.treePrev == null) { // compose(length, ::isOdd), val predicate = ::isOdd removeSingleWhiteSpace = true !prevLeaf.textContains('\n') && prevLeaf.psi.textLength > 1 } else { // String::length, List::isEmpty !prevLeaf.textContains('\n') } + } - else -> false + else -> { + false + } } val spacingAfter = nextLeaf is PsiWhiteSpace when { 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 a48f70df15..02ebc26f5b 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 @@ -53,7 +53,9 @@ public class SpacingAroundParensRule : StandardRule("paren-spacing") { private fun ASTNode.isUnexpectedSpacingBeforeParenthesis(): Boolean = when { - !prevLeaf().isWhiteSpaceWithoutNewline() -> false + !prevLeaf().isWhiteSpaceWithoutNewline() -> { + false + } elementType == LPAR -> { treeParent?.elementType in elementListTokenSet && @@ -72,7 +74,9 @@ public class SpacingAroundParensRule : StandardRule("paren-spacing") { prevLeaf()?.prevSibling()?.elementType != LPAR } - else -> false + else -> { + false + } } private fun ASTNode.isUnexpectedSpacingBetweenIdentifierAndElementList() = diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StatementWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StatementWrappingRule.kt index 4c91958844..cfa3387d90 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StatementWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StatementWrappingRule.kt @@ -64,19 +64,22 @@ public class StatementWrappingRule : emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (node.elementType) { - BLOCK -> + BLOCK -> { if (node.treeParent.elementType == FUNCTION_LITERAL) { // LBRACE and RBRACE are outside of BLOCK visitBlock(node.treeParent, emit) } else { visitBlock(node, emit) } + } - CLASS_BODY, WHEN -> + CLASS_BODY, WHEN -> { visitBlock(node, emit) + } - SEMICOLON -> + SEMICOLON -> { visitSemiColon(node, emit) + } } } 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 40e123e356..e22e4cab5b 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 @@ -149,7 +149,7 @@ public class TrailingCommaOnCallSiteRule : else -> if (trailingCommaNode != null) TrailingCommaState.REDUNDANT else TrailingCommaState.NOT_EXISTS } when (trailingCommaState) { - TrailingCommaState.EXISTS -> + TrailingCommaState.EXISTS -> { if (!isTrailingCommaAllowed) { emit( trailingCommaNode!!.startOffset, @@ -159,8 +159,9 @@ public class TrailingCommaOnCallSiteRule : this.removeChild(trailingCommaNode) } } + } - TrailingCommaState.MISSING -> + TrailingCommaState.MISSING -> { if (isTrailingCommaAllowed) { val prevNode = inspectNode.prevCodeLeaf()!! emit( @@ -176,6 +177,7 @@ public class TrailingCommaOnCallSiteRule : } } } + } TrailingCommaState.REDUNDANT -> { emit( @@ -187,7 +189,9 @@ public class TrailingCommaOnCallSiteRule : } } - TrailingCommaState.NOT_EXISTS -> Unit + 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 511e412698..80ae9adaf2 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 @@ -263,12 +263,16 @@ public class TrailingCommaOnDeclarationSiteRule : TrailingCommaState.NOT_EXISTS } - isMultiline(psi) -> if (trailingCommaNode != null) TrailingCommaState.EXISTS else TrailingCommaState.MISSING + isMultiline(psi) -> { + if (trailingCommaNode != null) TrailingCommaState.EXISTS else TrailingCommaState.MISSING + } - else -> if (trailingCommaNode != null) TrailingCommaState.REDUNDANT else TrailingCommaState.NOT_EXISTS + else -> { + if (trailingCommaNode != null) TrailingCommaState.REDUNDANT else TrailingCommaState.NOT_EXISTS + } } when (trailingCommaState) { - TrailingCommaState.EXISTS -> + TrailingCommaState.EXISTS -> { if (isTrailingCommaAllowed) { inspectNode .treeParent @@ -295,8 +299,9 @@ public class TrailingCommaOnDeclarationSiteRule : this.removeChild(trailingCommaNode) } } + } - TrailingCommaState.MISSING -> + TrailingCommaState.MISSING -> { if (isTrailingCommaAllowed) { val leafBeforeArrowOrNull = leafBeforeArrowOrNull() val addNewLine = @@ -354,6 +359,7 @@ public class TrailingCommaOnDeclarationSiteRule : } } } + } TrailingCommaState.REDUNDANT -> { emit( @@ -365,7 +371,9 @@ public class TrailingCommaOnDeclarationSiteRule : } } - TrailingCommaState.NOT_EXISTS -> Unit + TrailingCommaState.NOT_EXISTS -> { + Unit + } } } @@ -409,17 +417,21 @@ public class TrailingCommaOnDeclarationSiteRule : private fun ASTNode.leafBeforeArrowOrNull() = when (psi) { - is KtWhenEntry -> + is KtWhenEntry -> { (psi as KtWhenEntry) .arrow ?.prevLeaf() + } - is KtFunctionLiteral -> + is KtFunctionLiteral -> { (psi as KtFunctionLiteral) .arrow ?.prevLeaf() + } - else -> null + else -> { + null + } } private fun ASTNode.findPreviousTrailingCommaNodeOrNull(): ASTNode? { 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 6cd0159ab2..9d965c08db 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 @@ -59,8 +59,9 @@ public class TypeArgumentListSpacingRule : visitInsideTypeArgumentList(node, emit) } - ElementType.SUPER_TYPE_LIST, ElementType.SUPER_EXPRESSION -> + ElementType.SUPER_TYPE_LIST, ElementType.SUPER_EXPRESSION -> { visitInsideTypeArgumentList(node, 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 afe845fd6a..ef21b1a974 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 @@ -251,7 +251,9 @@ public class TypeParameterListSpacingRule : emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when { - node.text == " " -> Unit + node.text == " " -> { + Unit + } node.textContains('\n') -> { emit(node.startOffset, "Expected a single space instead of newline", true) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WhenEntryBracing.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WhenEntryBracing.kt new file mode 100644 index 0000000000..e8853ee310 --- /dev/null +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WhenEntryBracing.kt @@ -0,0 +1,169 @@ +package com.pinterest.ktlint.ruleset.standard.rules + +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision +import com.pinterest.ktlint.rule.engine.core.api.ElementType +import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARROW +import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK +import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHEN_ENTRY +import com.pinterest.ktlint.rule.engine.core.api.IndentConfig +import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler +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.INDENT_SIZE_PROPERTY +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPERTY +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed +import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace +import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline +import com.pinterest.ktlint.rule.engine.core.api.nextSibling +import com.pinterest.ktlint.rule.engine.core.api.prevSibling +import com.pinterest.ktlint.ruleset.standard.StandardRule +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtScript +import org.jetbrains.kotlin.psi.KtScriptInitializer +import org.jetbrains.kotlin.psi.KtWhenEntry +import org.jetbrains.kotlin.psi.KtWhenExpression +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType +import org.jetbrains.kotlin.psi.psiUtil.siblings + +/** + * If any when condition is using curly braces, then all other when conditions should use braces as well. + * + * Braces are helpful for following reasons: + * - Bodies of the when-conditions are all aligned at same column position + * - Closing braces helps in separation the when-conditions + */ +@SinceKtlint("1.4.0", EXPERIMENTAL) +public class WhenEntryBracing : + StandardRule( + id = "when-entry-bracing", + usesEditorConfigProperties = + setOf( + INDENT_SIZE_PROPERTY, + INDENT_STYLE_PROPERTY, + ), + ), + RuleAutocorrectApproveHandler, + Rule.OfficialCodeStyle, + Rule.Experimental { + private var indentConfig = IndentConfig.DEFAULT_INDENT_CONFIG + + override fun beforeFirstNode(editorConfig: EditorConfig) { + indentConfig = + IndentConfig( + indentStyle = editorConfig[INDENT_STYLE_PROPERTY], + tabWidth = editorConfig[INDENT_SIZE_PROPERTY], + ) + } + + override fun beforeVisitChildNodes( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + ) { + if (node.elementType == ElementType.WHEN) { + visitWhenStatement(node, emit) + } + } + + private fun visitWhenStatement( + node: ASTNode, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + ) { + if (node.hasAnyWhenEntryWithBlockAfterArrow() || node.hasAnyWhenEntryWithMultilineBody()) { + addBracesToWhenEntry(node, emitAndApprove) + } + } + + private fun ASTNode.hasAnyWhenEntryWithBlockAfterArrow() = children().any { it.elementType == WHEN_ENTRY && it.hasBlockAfterArrow() } + + private fun ASTNode.hasBlockAfterArrow(): Boolean { + require(elementType == WHEN_ENTRY) + return findChildByType(ARROW) + ?.siblings() + .orEmpty() + .any { it.elementType == BLOCK } + } + + private fun ASTNode.hasAnyWhenEntryWithMultilineBody() = children().any { it.elementType == WHEN_ENTRY && it.hasMultilineBody() } + + private fun ASTNode.hasMultilineBody(): Boolean { + require(elementType == WHEN_ENTRY) + return findChildByType(ARROW) + ?.siblings() + .orEmpty() + .any { it.isWhiteSpaceWithNewline() } + } + + private fun addBracesToWhenEntry( + node: ASTNode, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + ) { + node + .children() + .filter { it.elementType == WHEN_ENTRY } + .filter { !it.hasBlockAfterArrow() } + .forEach { whenEntry -> + whenEntry + .findChildByType(ARROW) + ?.let { arrow -> + val nonWhiteSpaceSibling = arrow.nextSibling { !it.isWhiteSpace() } ?: arrow + emitAndApprove( + nonWhiteSpaceSibling.startOffset, + "Body of when entry should be surrounded by braces if any when entry body is surrounded by braces " + + "or has a multiline body", + true, + ).ifAutocorrectAllowed { + arrow +// .nextSibling { it.isWhiteSpace() } + .surroundWithBraces() + } + } + } + } + + private fun ASTNode.surroundWithBraces() { + require(elementType == ARROW) + val whenEntryIndent = indentConfig.parentIndentOf(this).removePrefix("\n") + val whenEntry = + "${whenEntryIndent}true -> {" + + // Replace the whitespaces (possibly this could be a proper indent) at the beginning of the body with an indent. In case + // the body was already a multiline statement, then the second and following lines should already be properly indented. + indentConfig.childIndentOf(this) + + siblings() + .dropWhile { it.isWhiteSpace() } + .joinToString(separator = "") { it.text } + + "\n$whenEntryIndent}" + val blockExpression = createBlockExpression(whenEntry) + val prevSibling = prevSibling()!! + treeParent.removeRange(nextSibling()!!, null) + prevSibling.treeParent.addChild(PsiWhiteSpaceImpl(" "), null) + prevSibling.treeParent.addChild(blockExpression!!, null) + } + + private fun ASTNode.createBlockExpression(whenEntry: String) = + PsiFileFactory + .getInstance(psi.project) + .createFileFromText( + KotlinLanguage.INSTANCE, + """ + |when { + |$whenEntry + |} + """.trimMargin(), + ).getChildOfType() + ?.getChildOfType() + ?.getChildOfType() + ?.getChildOfType() + ?.getChildOfType() + ?.getChildOfType() + ?.node +} + +public val WHEN_ENTRY_BRACING_RULE_ID: RuleId = WhenEntryBracing().ruleId diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WhenEntryBracingTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WhenEntryBracingTest.kt new file mode 100644 index 0000000000..9284d9ec5d --- /dev/null +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WhenEntryBracingTest.kt @@ -0,0 +1,198 @@ +package com.pinterest.ktlint.ruleset.standard.rules + +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.LintViolation +import org.junit.jupiter.api.Test + +class WhenEntryBracingTest { + private val whenEntryBracingRuleAssertThat = assertThatRule { WhenEntryBracing() } + + @Test + fun `Given a when-statement for which no entry has a block body then do not reformat`() { + val code = + """ + val foo = + when (bar) { + BAR1 -> "bar1" + BAR2 -> "bar2" + else -> null + } + """.trimIndent() + whenEntryBracingRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a when-statement for which all entries have a block body then do not reformat`() { + val code = + """ + val foo = + when (bar) { + BAR1 -> { "bar1" } + BAR2 -> { "bar2" } + else -> { null } + } + """.trimIndent() + whenEntryBracingRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a when-statement containing an entry with braces and an entry without braces then add braces to all entries`() { + 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") + whenEntryBracingRuleAssertThat(code) + .addAdditionalRuleProvider { + // Ensures that the first when entry is also wrapped to a multiline body + StatementWrappingRule() + }.hasLintViolations( + LintViolation(4, 17, "Body of when entry should be surrounded by braces if any when entry body is surrounded by braces or has a multiline body"), + LintViolation(5, 17, "Body of when entry should be surrounded by braces if any when entry body is surrounded by braces or has a multiline body"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a when-statement containing an entry with braces and an entry without braces which contains one multiline statement then add braces to all entries`() { + val code = + """ + val foo = + when (bar) { + BAR1 -> { "bar1" } + BAR2 -> "bar2" + .plus("bar3") + .plus("bar4") + else -> null + } + """.trimIndent() + val formattedCode = + """ + val foo = + when (bar) { + BAR1 -> { + "bar1" + } + BAR2 -> { + "bar2" + .plus("bar3") + .plus("bar4") + } + else -> { + null + } + } + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + whenEntryBracingRuleAssertThat(code) + .addAdditionalRuleProvider { + // Ensures that the first when entry is also wrapped to a multiline body + StatementWrappingRule() + }.addAdditionalRuleProvider { + // Fix indent of the wrapped multiline statement + IndentationRule() + }.hasLintViolations( + LintViolation(4, 17, "Body of when entry should be surrounded by braces if any when entry body is surrounded by braces or has a multiline body"), + LintViolation(7, 17, "Body of when entry should be surrounded by braces if any when entry body is surrounded by braces or has a multiline body"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a when-statement containing an entry with braces and an entry without braces which starts with some EOL comments then add braces to all entries`() { + val code = + """ + val foo = + when (bar) { + BAR1 -> { "bar1" } + BAR2 -> // some comment 1 + // some comment 2 + "bar2" + else -> null + } + """.trimIndent() + val formattedCode = + """ + val foo = + when (bar) { + BAR1 -> { + "bar1" + } + BAR2 -> { + // some comment 1 + // some comment 2 + "bar2" + } + else -> { + null + } + } + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + whenEntryBracingRuleAssertThat(code) + .addAdditionalRuleProvider { + // Ensures that the first when entry is also wrapped to a multiline body + StatementWrappingRule() + }.hasLintViolations( + LintViolation(4, 17, "Body of when entry should be surrounded by braces if any when entry body is surrounded by braces or has a multiline body"), + LintViolation(7, 17, "Body of when entry should be surrounded by braces if any when entry body is surrounded by braces or has a multiline body"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a when-statement with a multiline body not contained in a block then add braces to all entries`() { + 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") + whenEntryBracingRuleAssertThat(code) + .addAdditionalRuleProvider { + // Ensures that the first when entry is also wrapped to a multiline body + StatementWrappingRule() + }.hasLintViolations( + LintViolation(3, 17, "Body of when entry should be surrounded by braces if any when entry body is surrounded by braces or has a multiline body"), + LintViolation(5, 13, "Body of when entry should be surrounded by braces if any when entry body is surrounded by braces or has a multiline body"), + LintViolation(6, 17, "Body of when entry should be surrounded by braces if any when entry body is surrounded by braces or has a multiline body"), + ).isFormattedAs(formattedCode) + } +}