From 4720ab2bad55608c86ab929132fb2953af506470 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 27 Feb 2024 11:44:29 +0100 Subject: [PATCH 1/2] Run `argument-list-wrapping` after `function-signature` Closes #2454 --- .../rules/ArgumentListWrappingRule.kt | 1 + .../standard/rules/FunctionSignatureRule.kt | 7 - .../rules/ArgumentListWrappingRuleTest.kt | 214 +++++++++++++++++- 3 files changed, 213 insertions(+), 9 deletions(-) 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 23e04dc4ac..9ca13e70dd 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 @@ -69,6 +69,7 @@ public class ArgumentListWrappingRule : RunAfterRule(VALUE_ARGUMENT_COMMENT_RULE_ID, REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED), RunAfterRule(WRAPPING_RULE_ID, REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED), RunAfterRule(CLASS_SIGNATURE_RULE_ID, REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED), + RunAfterRule(FUNCTION_SIGNATURE_RULE_ID, REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED), ), usesEditorConfigProperties = setOf( 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 83099a838c..fdd491c187 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 @@ -38,7 +38,6 @@ import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.nextCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.nextCodeSibling import com.pinterest.ktlint.rule.engine.core.api.nextLeaf -import com.pinterest.ktlint.rule.engine.core.api.prevCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.rule.engine.core.api.prevSibling import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe @@ -733,12 +732,6 @@ public class FunctionSignatureRule : .count { it.elementType == VALUE_PARAMETER } } - private fun ASTNode.getLastNodeOfFunctionSignatureWithBlockBody(): ASTNode? = - this - .findChildByType(BLOCK) - ?.firstChildNode - ?.prevCodeLeaf() - public companion object { private const val FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY_UNSET = Int.MAX_VALUE public val FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY: EditorConfigProperty = diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt index 9386e2700f..1177ab1f02 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt @@ -3,7 +3,9 @@ package com.pinterest.ktlint.ruleset.standard.rules import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import com.pinterest.ktlint.ruleset.standard.rules.ArgumentListWrappingRule.Companion.IGNORE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY -import com.pinterest.ktlint.ruleset.standard.rules.ClassSignatureRule.Companion.FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY +import com.pinterest.ktlint.ruleset.standard.rules.FunctionSignatureRule.Companion.FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY +import com.pinterest.ktlint.ruleset.standard.rules.FunctionSignatureRule.FunctionBodyExpressionWrapping +import com.pinterest.ktlint.ruleset.standard.rules.FunctionSignatureRule.FunctionBodyExpressionWrapping.default import com.pinterest.ktlint.test.KtLintAssertThat.Companion.EOL_CHAR import com.pinterest.ktlint.test.KtLintAssertThat.Companion.MAX_LINE_LENGTH_MARKER import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder @@ -11,6 +13,9 @@ import com.pinterest.ktlint.test.LintViolation import com.pinterest.ktlint.test.MULTILINE_STRING_QUOTE import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import com.pinterest.ktlint.ruleset.standard.rules.ClassSignatureRule.Companion.FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY as CLASS_SIGNATURE_FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY class ArgumentListWrappingRuleTest { private val argumentListWrappingRuleAssertThat = @@ -931,11 +936,12 @@ class ArgumentListWrappingRuleTest { // body } """.trimIndent() + @Suppress("ktlint:standard:max-line-length") argumentListWrappingRuleAssertThat(code) .setMaxLineLength() .addAdditionalRuleProvider { ClassSignatureRule() } .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) - .withEditorConfigOverride(FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY to 4) + .withEditorConfigOverride(CLASS_SIGNATURE_FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY to 4) .hasLintViolationForAdditionalRule(5, 37, "Super type should start on a newline") .hasLintViolations( LintViolation(5, 44, "Argument should be on a separate line (unless all arguments can fit a single line)"), @@ -1010,4 +1016,208 @@ class ArgumentListWrappingRuleTest { LintViolation(2, 45, "Exceeded max line length (44)", false), ) } + + @Nested + inner class `Issue 2450 - Run 'argument-list-wrapping' after 'function-signature'` { + @Nested + inner class `Given default body expression wrapping` { + @Test + fun `Given a single line function signature`() { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo(bar: String): Foo = Foo.baz(a = "looooooooooooooooooooong", b = "looooooooooooooooooooong") + """.trimIndent() + val formattedCode = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo(bar: String): Foo = Foo.baz( + a = "looooooooooooooooooooong", + b = "looooooooooooooooooooong" + ) + """.trimIndent() + argumentListWrappingRuleAssertThat(code) + .setMaxLineLength() + .addAdditionalRuleProvider { FunctionSignatureRule() } + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY to default) + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a multiline function signature`() { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo( + bar1: String, + bar2: String, + ): Foo = Foo.baz( + a = "looooooooooooooooooooong", + b = "looooooooooooooooooooong", + ) + """.trimIndent() + argumentListWrappingRuleAssertThat(code) + .setMaxLineLength() + .addAdditionalRuleProvider { FunctionSignatureRule() } + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY to default) + .hasNoLintViolations() + } + + @Test + fun `Given a multiline function signature that should be single line`() { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo( + bar: String + ): Foo = Foo.baz( + a = "looooooooooooooooooooong", + b = "looooooooooooooooooooong" + ) + """.trimIndent() + val formattedCode = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo(bar: String): Foo = + Foo.baz( + a = "looooooooooooooooooooong", + b = "looooooooooooooooooooong" + ) + """.trimIndent() + argumentListWrappingRuleAssertThat(code) + .setMaxLineLength() + .addAdditionalRuleProvider { FunctionSignatureRule() } + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY to default) + .hasNoLintViolationsExceptInAdditionalRules() + .isFormattedAs(formattedCode) + } + + @Test + fun `Given function and body which do not need wrapping as line length is not exceeded`() { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo(bar: String): String = + Foo.baz(a = "a", b = "b") + """.trimIndent() + argumentListWrappingRuleAssertThat(code) + .setMaxLineLength() + .addAdditionalRuleProvider { FunctionSignatureRule() } + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY to default) + .hasNoLintViolations() + } + } + + @Nested + inner class `Given non-default body expression wrapping` { + @ParameterizedTest(name = "bodyExpressionWrapping: {0}") + @EnumSource( + value = FunctionBodyExpressionWrapping::class, + mode = EnumSource.Mode.EXCLUDE, + names = ["default"], + ) + fun `Given a single line function signature`(functionBodyExpressionWrapping: FunctionBodyExpressionWrapping) { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo(bar: String): Foo = Foo.baz(a = "looooooooooooooooooooong", b = "looooooooooooooooooooong") + """.trimIndent() + val formattedCode = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo(bar: String): Foo = + Foo.baz( + a = "looooooooooooooooooooong", + b = "looooooooooooooooooooong" + ) + """.trimIndent() + argumentListWrappingRuleAssertThat(code) + .setMaxLineLength() + .addAdditionalRuleProvider { FunctionSignatureRule() } + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY to functionBodyExpressionWrapping) + .isFormattedAs(formattedCode) + } + + @ParameterizedTest(name = "bodyExpressionWrapping: {0}") + @EnumSource( + value = FunctionBodyExpressionWrapping::class, + mode = EnumSource.Mode.EXCLUDE, + names = ["default"], + ) + fun `Given a multiline function signature`(functionBodyExpressionWrapping: FunctionBodyExpressionWrapping) { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo( + bar1: String, + bar2: String, + ): Foo = Foo.baz( + a = "looooooooooooooooooooong", + b = "looooooooooooooooooooong", + ) + """.trimIndent() + val formattedCode = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo( + bar1: String, + bar2: String, + ): Foo = + Foo.baz( + a = "looooooooooooooooooooong", + b = "looooooooooooooooooooong", + ) + """.trimIndent() + argumentListWrappingRuleAssertThat(code) + .setMaxLineLength() + .addAdditionalRuleProvider { FunctionSignatureRule() } + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY to functionBodyExpressionWrapping) + .hasNoLintViolationsExceptInAdditionalRules() + .isFormattedAs(formattedCode) + } + + @ParameterizedTest(name = "bodyExpressionWrapping: {0}") + @EnumSource( + value = FunctionBodyExpressionWrapping::class, + mode = EnumSource.Mode.EXCLUDE, + names = ["default"], + ) + fun `Given a multiline function signature that should be single line`( + functionBodyExpressionWrapping: FunctionBodyExpressionWrapping, + ) { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo( + bar: String + ): Foo = Foo.baz( + a = "looooooooooooooooooooong", + b = "looooooooooooooooooooong" + ) + """.trimIndent() + val formattedCode = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo(bar: String): Foo = + Foo.baz( + a = "looooooooooooooooooooong", + b = "looooooooooooooooooooong" + ) + """.trimIndent() + argumentListWrappingRuleAssertThat(code) + .setMaxLineLength() + .addAdditionalRuleProvider { FunctionSignatureRule() } + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY to functionBodyExpressionWrapping) + .hasNoLintViolationsExceptInAdditionalRules() + .isFormattedAs(formattedCode) + } + } + } } From 67959770855f7ed8f9dff6db7c85a6c93db2e076 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 27 Feb 2024 12:31:10 +0100 Subject: [PATCH 2/2] Replace `Enum.values()` with `Enum.values` --- .../ktlint/cli/reporter/format/FormatReporterProvider.kt | 2 +- .../ktlint/cli/reporter/plain/PlainReporterProvider.kt | 2 +- .../core/api/editorconfig/CodeStyleEditorConfigProperty.kt | 2 +- .../core/api/editorconfig/RuleExecutionEditorConfigProperty.kt | 2 +- .../rule/engine/core/api/editorconfig/EditorConfigTest.kt | 2 +- .../engine/core/api/editorconfig/SafeEnumValueParserTest.kt | 2 +- .../ktlint/ruleset/standard/rules/FunctionSignatureRule.kt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporterProvider.kt b/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporterProvider.kt index 5239640661..d78a916af2 100644 --- a/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporterProvider.kt +++ b/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporterProvider.kt @@ -23,7 +23,7 @@ public class FormatReporterProvider : ReporterProviderV2 { private fun String.emptyOrTrue() = this == "" || this == "true" private fun getColor(color: String?): Color = - Color.values().firstOrNull { + Color.entries.firstOrNull { it.name == color } ?: throw IllegalArgumentException("Invalid color parameter.") } diff --git a/ktlint-cli-reporter-plain/src/main/kotlin/com/pinterest/ktlint/cli/reporter/plain/PlainReporterProvider.kt b/ktlint-cli-reporter-plain/src/main/kotlin/com/pinterest/ktlint/cli/reporter/plain/PlainReporterProvider.kt index f3802c7282..befc65dd7e 100644 --- a/ktlint-cli-reporter-plain/src/main/kotlin/com/pinterest/ktlint/cli/reporter/plain/PlainReporterProvider.kt +++ b/ktlint-cli-reporter-plain/src/main/kotlin/com/pinterest/ktlint/cli/reporter/plain/PlainReporterProvider.kt @@ -21,7 +21,7 @@ public class PlainReporterProvider : ReporterProviderV2 { private fun String.emptyOrTrue() = this == "" || this == "true" private fun getColor(colorInput: String?): Color = - Color.values().firstOrNull { + Color.entries.firstOrNull { it.name == colorInput } ?: throw IllegalArgumentException("Invalid color parameter.") } diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/CodeStyleEditorConfigProperty.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/CodeStyleEditorConfigProperty.kt index 131b2d2fa6..ecdb4f70b6 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/CodeStyleEditorConfigProperty.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/CodeStyleEditorConfigProperty.kt @@ -40,7 +40,7 @@ public val CODE_STYLE_PROPERTY_TYPE: PropertyType.LowerCasingPropertyType = diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/RuleExecutionEditorConfigProperty.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/RuleExecutionEditorConfigProperty.kt index b5b7104537..1cc4047913 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/RuleExecutionEditorConfigProperty.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/RuleExecutionEditorConfigProperty.kt @@ -16,7 +16,7 @@ public val RULE_EXECUTION_PROPERTY_TYPE: PropertyType.LowerCasingPropertyType