From 9b9dbd8d026c780406292a264da68dc5e14a0546 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 2 Apr 2023 16:34:25 +0200 Subject: [PATCH] Wrap annotated function parameters and projection types (#1910) Wrap annotated function parameters to a separate line in code style `ktlint_official` only Closes #1908 Wrap annotated projection types in type argument lists to a separate line `annotation` Closes #1909 --- CHANGELOG.md | 2 + .../ruleset/standard/rules/AnnotationRule.kt | 84 +++++++++--------- .../standard/rules/FunctionSignatureRule.kt | 28 ++++-- .../rules/ParameterListWrappingRule.kt | 35 +++++--- .../standard/rules/AnnotationRuleTest.kt | 47 +++++----- .../rules/FunctionSignatureRuleTest.kt | 64 +++++++++----- .../standard/rules/IndentationRuleTest.kt | 6 +- .../rules/ParameterListWrappingRuleTest.kt | 85 ++++++++++++++----- 8 files changed, 223 insertions(+), 128 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03930da96d..f1620cee6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -277,6 +277,8 @@ Previously the default value for `.editorconfig` property `max_line_length` was * Allow value arguments with a multiline expression to be indented on a separate line `indent` ([#1217](https://github.com/pinterest/ktlint/issues/1217)) * When enabled, the ktlint rule checking is disabled for all code surrounded by the formatter tags (see [faq](https://pinterest.github.io/ktlint/faq/#are-formatter-tags-respected)) ([#670](https://github.com/pinterest/ktlint/issues/670)) * Remove trailing comma if last two enum entries are on the same line and trailing commas are not allowed. `trailing-comma-on-declaration-site` ([#1905](https://github.com/pinterest/ktlint/issues/1905)) +* Wrap annotated function parameters to a separate line in code style `ktlint_official` only. `function-signature`, `parameter-list-wrapping` ([#1908](https://github.com/pinterest/ktlint/issues/1908)) +* Wrap annotated projection types in type argument lists to a separate line `annotation` ([#1909](https://github.com/pinterest/ktlint/issues/1909)) ### Changed * Wrap the parameters of a function literal containing a multiline parameter list (only in `ktlint_official` code style) `parameter-list-wrapping` ([#1681](https://github.com/pinterest/ktlint/issues/1681)). 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 c2470f471f..bdddca0835 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 @@ -6,7 +6,6 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.FILE_ANNOTATION_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.MODIFIER_LIST -import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_ARGUMENT_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PROJECTION import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_REFERENCE import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT @@ -18,24 +17,24 @@ import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRu import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.children +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY 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.firstChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.isCodeLeaf -import com.pinterest.ktlint.rule.engine.core.api.isPartOf 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.lastChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.nextCodeLeaf 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.upsertWhitespaceAfterMe import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe import com.pinterest.ktlint.rule.engine.core.util.safeAs import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget @@ -90,9 +89,11 @@ public class AnnotationRule : ), ), ) { + private var codeStyle = CODE_STYLE_PROPERTY.defaultValue private var indentConfig = IndentConfig.DEFAULT_INDENT_CONFIG override fun beforeFirstNode(editorConfig: EditorConfig) { + codeStyle = editorConfig[CODE_STYLE_PROPERTY] indentConfig = IndentConfig( indentStyle = editorConfig[INDENT_STYLE_PROPERTY], tabWidth = editorConfig[INDENT_SIZE_PROPERTY], @@ -127,11 +128,18 @@ public class AnnotationRule : require(node.elementType == ANNOTATION_ENTRY) if (node.isAnnotationEntryWithValueArgumentList() && - node.treeParent.treeParent.elementType != VALUE_PARAMETER && // fun fn(@Ann("blah") a: String) - node.treeParent.treeParent.elementType != VALUE_ARGUMENT && // fn(@Ann("blah") "42") - !node.isPartOf(TYPE_ARGUMENT_LIST) && // val property: Map<@Ann("blah") String, Int> + node.treeParent.treeParent.elementType != VALUE_PARAMETER && + node.treeParent.treeParent.elementType != VALUE_ARGUMENT && node.isNotReceiverTargetAnnotation() ) { + // Disallow: + // @Foo class Bar + // Allow function parameters and arguments to have annotation on same line as identifier: + // fun @receiver:Bar String.foo() {} + // fun foo( + // @Bar("bar") bar, + // @Bar("bar") "42", + // ) {} checkForAnnotationWithParameterToBePlacedOnSeparateLine(node, emit, autoCorrect) } @@ -154,6 +162,8 @@ public class AnnotationRule : } if (node.treeParent.elementType != ANNOTATION && + node.treeParent.treeParent.elementType != VALUE_PARAMETER && + node.treeParent.treeParent.elementType != VALUE_ARGUMENT && node.isPrecededByOtherAnnotationEntryOnTheSameLine() && node.isLastAnnotationEntry() ) { @@ -161,6 +171,7 @@ public class AnnotationRule : // @Foo1 @Foo2 fun foo() {} // But following is allowed: // @[Foo1 Foo2] fun foo() {} + // fun foo(@Bar1 @Bar2 bar) {} emit( node.findAnnotatedConstruct().startOffset, "Multiple annotations should not be placed on the same line as the annotated construct", @@ -179,24 +190,21 @@ public class AnnotationRule : } } - node - .typeProjectionOrNull() - ?.let { typeProjection -> + node.typeProjectionOrNull() + ?.prevCodeLeaf() + ?.let { startOfList -> // Code below is disallowed // var foo: List<@Foo1 @Foo2 String> // But following is allowed: // var foo: List<@[Foo1 Foo2] String> + // fun foo(@Bar1 @Bar2 bar) {} if (node.isFollowedByOtherAnnotationEntryOnTheSameLine() && node.isFirstAnnotationEntry() ) { - emit( - typeProjection.startOffset, - "Annotations on a type reference should be placed on a separate line", - true, - ) + emit(startOfList.startOffset, "Expected newline after '${startOfList.text}'", true) if (autoCorrect) { - typeProjection - .upsertWhitespaceBeforeMe( + startOfList + .upsertWhitespaceAfterMe( getNewlineWithIndent(node.treeParent).plus(indentConfig.indent), ) } @@ -204,18 +212,17 @@ public class AnnotationRule : if (node.isPrecededByOtherAnnotationEntryOnTheSameLine() && node.isLastAnnotationEntry() ) { - val annotatedConstruct = node.findAnnotatedConstruct() - emit( - annotatedConstruct.startOffset, - "Annotations on a type reference should be placed on a separate line", - true, - ) - if (autoCorrect) { - annotatedConstruct - .nextLeaf { it.isCodeLeaf() && it.elementType != ElementType.COMMA }!! - .firstChildLeafOrSelf() - .upsertWhitespaceBeforeMe(getNewlineWithIndent(node.treeParent)) - } + node + .findAnnotatedConstruct() + .treeParent + .lastChildLeafOrSelf() + .nextLeaf { it.isCodeLeaf() && it.elementType != ElementType.COMMA } + ?.let { codeLeaf -> + emit(codeLeaf.startOffset, "Expected newline before '${codeLeaf.text}'", true) + if (autoCorrect) { + codeLeaf.upsertWhitespaceBeforeMe(getNewlineWithIndent(node.treeParent)) + } + } } } } @@ -429,16 +436,17 @@ public class AnnotationRule : } private fun getNewlineWithIndent(modifierListRoot: ASTNode): String { - val nodeBeforeAnnotations = modifierListRoot.treeParent.treePrev as? PsiWhiteSpace - // If there is no whitespace before the annotation, the annotation is the first - // text in the file - val newLineWithIndent = nodeBeforeAnnotations?.text ?: "\n" - return if (newLineWithIndent.contains('\n')) { - // Make sure we only insert a single newline - newLineWithIndent.substring(newLineWithIndent.lastIndexOf('\n')) - } else { - newLineWithIndent - } + val nodeBeforeAnnotations = + modifierListRoot + .treeParent + .treePrev + // Make sure we only insert a single newline + val indentWithoutNewline = + nodeBeforeAnnotations + ?.text + .orEmpty() + .substringAfterLast('\n') + return "\n".plus(indentWithoutNewline) } } 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 33bb82c912..4b2eb8a844 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 @@ -19,6 +19,8 @@ import com.pinterest.ktlint.rule.engine.core.api.IndentConfig.Companion.DEFAULT_ import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.children +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue.ktlint_official 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.editorconfig.INDENT_SIZE_PROPERTY @@ -31,7 +33,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.nextSibling 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 @@ -66,6 +67,7 @@ public class FunctionSignatureRule : ), ), Rule.Experimental { + private var codeStyle = CODE_STYLE_PROPERTY.defaultValue private var indentConfig = DEFAULT_INDENT_CONFIG private var maxLineLength = MAX_LINE_LENGTH_PROPERTY.defaultValue private var functionSignatureWrappingMinimumParameters = @@ -73,6 +75,7 @@ public class FunctionSignatureRule : private var functionBodyExpressionWrapping = FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY.defaultValue override fun beforeFirstNode(editorConfig: EditorConfig) { + codeStyle = editorConfig[CODE_STYLE_PROPERTY] functionSignatureWrappingMinimumParameters = editorConfig[ FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY, ] @@ -159,7 +162,8 @@ public class FunctionSignatureRule : val forceMultilineSignature = node.hasMinimumNumberOfParameters() || - node.containsParameterPrecededByAnnotationOnSeparateLine() + node.containsMultilineParameter() || + (codeStyle == ktlint_official && node.containsAnnotatedParameter()) if (isMaxLineLengthSet()) { val singleLineFunctionSignatureLength = calculateFunctionSignatureLengthAsSingleLineSignature(node, emit, autoCorrect) // Function signatures not having parameters, should not be reformatted automatically. It would result in function signatures @@ -215,15 +219,25 @@ public class FunctionSignatureRule : tailNodesOfFunctionSignature.sumOf { it.text.length } } - private fun ASTNode.containsParameterPrecededByAnnotationOnSeparateLine(): Boolean = + private fun ASTNode.containsMultilineParameter(): Boolean = findChildByType(VALUE_PARAMETER_LIST) ?.children() .orEmpty() .filter { it.elementType == VALUE_PARAMETER } - .mapNotNull { - // If the value parameter contains a modifier then this list is followed by a white space - it.findChildByType(MODIFIER_LIST)?.nextSibling() - }.any { it.textContains('\n') } + .any { it.textContains('\n') } + + private fun ASTNode.containsAnnotatedParameter(): Boolean = + findChildByType(VALUE_PARAMETER_LIST) + ?.children() + .orEmpty() + .filter { it.elementType == VALUE_PARAMETER } + .any { it.isAnnotated() } + + private fun ASTNode.isAnnotated() = + findChildByType(MODIFIER_LIST) + ?.children() + .orEmpty() + .any { it.elementType == ANNOTATION_ENTRY } private fun calculateFunctionSignatureLengthAsSingleLineSignature( node: ASTNode, 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 0b3fcae21d..fc45fa3ca6 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 @@ -1,5 +1,6 @@ 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.FUNCTION_LITERAL import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_TYPE import com.pinterest.ktlint.rule.engine.core.api.ElementType.LPAR @@ -116,17 +117,14 @@ public class ParameterListWrappingRule : } private fun ASTNode.needToWrapParameterList() = - if (hasNoParameters() || - isPartOfFunctionLiteralInNonKtlintOfficialCodeStyle() || - isFunctionTypeWrappedInNullableType() - ) { - false - } else { - // each parameter should be on a separate line if - // - at least one of the parameters is - // - maxLineLength exceeded (and separating parameters with \n would actually help) - // in addition, "(" and ")" must be on separates line if any of the parameters are (otherwise on the same) - textContains('\n') || this.exceedsMaxLineLength() + when { + hasNoParameters() -> false + isPartOfFunctionLiteralInNonKtlintOfficialCodeStyle() -> false + isFunctionTypeWrappedInNullableType() -> false + textContains('\n') -> true + codeStyle == ktlint_official && containsAnnotatedParameter() -> true + exceedsMaxLineLength() -> true + else -> false } private fun ASTNode.hasNoParameters(): Boolean { @@ -144,6 +142,19 @@ public class ParameterListWrappingRule : return treeParent.elementType == FUNCTION_TYPE && treeParent?.treeParent?.elementType == NULLABLE_TYPE } + private fun ASTNode.containsAnnotatedParameter(): Boolean { + require(elementType == VALUE_PARAMETER_LIST) + return this.children() + .filter { it.elementType == VALUE_PARAMETER } + .any { it.isAnnotated() } + } + + private fun ASTNode.isAnnotated() = + findChildByType(ElementType.MODIFIER_LIST) + ?.children() + .orEmpty() + .any { it.elementType == ElementType.ANNOTATION_ENTRY } + private fun wrapParameterList( node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, @@ -243,7 +254,7 @@ public class ParameterListWrappingRule : when (node.elementType) { LPAR -> """Unnecessary newline before "("""" VALUE_PARAMETER -> - "Parameter should be on a separate line (unless all parameters can fit a single line)" + "Parameter should start on a newline" RPAR -> """Missing newline before ")"""" else -> throw UnsupportedOperationException() } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRuleTest.kt index 95a7a14fc3..57c1aa1ef4 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRuleTest.kt @@ -141,13 +141,6 @@ class AnnotationRuleTest { @[Foo Bar] fun bar() {} } """.trimIndent() - val formattedCode = - """ - @[Foo Bar] class FooBar2 { - @[Foo Bar] var foo: String - @[Foo Bar] fun bar() {} - } - """.trimIndent() annotationRuleAssertThat(code) .hasNoLintViolations() } @@ -347,19 +340,15 @@ class AnnotationRuleTest { } @Test - fun `Issue 642 - Given annotations on type arguments on same line as argument`() { + fun `Given a function signature having annotated parameters, then allow the annotation to be on the same line as the annotated construct`() { val code = """ - val aProperty: Map<@Ann("test") Int, @JvmSuppressWildcards(true) (String) -> Int?> - val bProperty: Map< - @Ann String, - @Ann("test") Int, - @JvmSuppressWildcards(true) (String) -> Int? - > - - fun doSomething() { - funWithGenericsCall<@JvmSuppressWildcards(true) Int>() - } + fun foo( + a: int, + @Bar1 b: int, + @Bar1 @Bar2 c: int, + @Bar3("bar3") @Bar1 d: int + ) {} """.trimIndent() annotationRuleAssertThat(code).hasNoLintViolations() } @@ -662,9 +651,9 @@ class AnnotationRuleTest { .addAdditionalRuleProvider { TrailingCommaOnDeclarationSiteRule() } .addAdditionalRuleProvider { WrappingRule() } .hasLintViolations( - LintViolation(1, 18, "Annotations on a type reference should be placed on a separate line"), + LintViolation(1, 17, "Expected newline after '<'"), LintViolation(1, 28, "Multiple annotations should not be placed on the same line as the annotated construct"), - LintViolation(1, 28, "Annotations on a type reference should be placed on a separate line"), + LintViolation(1, 34, "Expected newline before '>'"), ).isFormattedAs(formattedCode) } @@ -672,24 +661,32 @@ class AnnotationRuleTest { fun `Given a custom type with multiple annotations on it type parameter(s)`() { val code = """ - val fooBar: FooBar = emptyList() + val fooBar: FooBar = FooBar() """.trimIndent() val formattedCode = """ val fooBar: FooBar< String, + @Foo String, @Foo @Bar String, + @Bar("bar") + @Foo String - > = emptyList() + > = FooBar() """.trimIndent() + @Suppress("ktlint:argument-list-wrapping", "ktlint:max-line-length") annotationRuleAssertThat(code) .addAdditionalRuleProvider { IndentationRule() } .addAdditionalRuleProvider { WrappingRule() } .hasLintViolations( - LintViolation(1, 28, "Annotations on a type reference should be placed on a separate line"), - LintViolation(1, 38, "Multiple annotations should not be placed on the same line as the annotated construct"), - LintViolation(1, 38, "Annotations on a type reference should be placed on a separate line"), + LintViolation(1, 39, "Expected newline after ','"), + LintViolation(1, 51, "Multiple annotations should not be placed on the same line as the annotated construct"), + LintViolation(1, 57, "Expected newline after ','"), + LintViolation(1, 59, "Expected newline before '@'"), + LintViolation(1, 59, "Annotation with parameter(s) should be placed on a separate line prior to the annotated construct"), + LintViolation(1, 76, "Multiple annotations should not be placed on the same line as the annotated construct"), + LintViolation(1, 82, "Expected newline before '>'"), ).isFormattedAs(formattedCode) } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt index a73550ba29..26c5a3703c 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt @@ -1,5 +1,8 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue.ktlint_official import com.pinterest.ktlint.ruleset.standard.rules.FunctionSignatureRule.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.test.KtLintAssertThat.Companion.EOL_CHAR @@ -975,26 +978,47 @@ class FunctionSignatureRuleTest { .hasNoLintViolations() } - @Test - fun `Given a function signature with an annotated parameter and the annotation is on the same line as the parameter then reformat it as a single line signature when it fits`() { - val code = - """ - // $MAX_LINE_LENGTH_MARKER $EOL_CHAR - fun foo( - @Bar bar: String - ) = "some-result" - """.trimIndent() - val formattedCode = - """ - // $MAX_LINE_LENGTH_MARKER $EOL_CHAR - fun foo(@Bar bar: String) = "some-result" - """.trimIndent() - functionSignatureWrappingRuleAssertThat(code) - .setMaxLineLength() - .hasLintViolations( - LintViolation(3, 5, "No whitespace expected between opening parenthesis and first parameter name"), - LintViolation(3, 21, "No whitespace expected between last parameter and closing parenthesis"), - ).isFormattedAs(formattedCode) + @Nested + inner class `Given a single line function signature with an annotated parameter` { + @Test + fun `Given ktlint_official code style`() { + val code = + """ + fun foo(a: Int, @Bar bar: String, b: Int) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo( + a: Int, + @Bar bar: String, + b: Int + ) = "some-result" + """.trimIndent() + functionSignatureWrappingRuleAssertThat(code) + .withEditorConfigOverride(CODE_STYLE_PROPERTY to ktlint_official) + .hasLintViolations( + LintViolation(1, 9, "Newline expected after opening parenthesis"), + LintViolation(1, 17, "Parameter should start on a newline"), + LintViolation(1, 35, "Parameter should start on a newline"), + LintViolation(1, 41, "Newline expected before closing parenthesis"), + ).isFormattedAs(formattedCode) + } + + @ParameterizedTest(name = "Code style: {0}") + @EnumSource( + value = CodeStyleValue::class, + mode = EnumSource.Mode.EXCLUDE, + names = ["ktlint_official"], + ) + fun `Given non-ktlint_official code style`(codeStyle: CodeStyleValue) { + val code = + """ + fun foo(a: Int, @Bar bar: String, b: Int) = "some-result" + """.trimIndent() + functionSignatureWrappingRuleAssertThat(code) + .withEditorConfigOverride(CODE_STYLE_PROPERTY to codeStyle) + .hasNoLintViolations() + } } @Test diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt index e878eef563..01dbefdfd5 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt @@ -4820,11 +4820,7 @@ internal class IndentationRuleTest { indentationRuleAssertThat(code) .addAdditionalRuleProvider { ParameterListWrappingRule() } .withEditorConfigOverride(CODE_STYLE_PROPERTY to ktlint_official) - .hasLintViolationForAdditionalRule( - 2, - 20, - "Parameter should be on a separate line (unless all parameters can fit a single line)", - ) + .hasLintViolationForAdditionalRule(2, 20, "Parameter should start on a newline") .hasLintViolations( LintViolation(3, 1, "Unexpected indentation (19) (should be 12)"), LintViolation(4, 1, "Unexpected indentation (19) (should be 12)"), diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt index d3be0b2997..5cc3aba446 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt @@ -41,8 +41,8 @@ class ParameterListWrappingRuleTest { """.trimIndent() parameterListWrappingRuleAssertThat(code) .hasLintViolations( - LintViolation(1, 14, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(1, 30, "Parameter should be on a separate line (unless all parameters can fit a single line)"), + LintViolation(1, 14, "Parameter should start on a newline"), + LintViolation(1, 30, "Parameter should start on a newline"), LintViolation(2, 28, """Missing newline before ")""""), ).isFormattedAs(formattedCode) } @@ -64,9 +64,9 @@ class ParameterListWrappingRuleTest { parameterListWrappingRuleAssertThat(code) .withEditorConfigOverride(MAX_LINE_LENGTH_PROPERTY to 10) .hasLintViolations( - LintViolation(1, 14, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(1, 30, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(1, 46, "Parameter should be on a separate line (unless all parameters can fit a single line)"), + LintViolation(1, 14, "Parameter should start on a newline"), + LintViolation(1, 30, "Parameter should start on a newline"), + LintViolation(1, 46, "Parameter should start on a newline"), LintViolation(1, 60, """Missing newline before ")""""), ).isFormattedAs(formattedCode) } @@ -124,7 +124,7 @@ class ParameterListWrappingRuleTest { """.trimIndent() parameterListWrappingRuleAssertThat(code) .hasLintViolations( - LintViolation(1, 7, "Parameter should be on a separate line (unless all parameters can fit a single line)"), + LintViolation(1, 7, "Parameter should start on a newline"), LintViolation(3, 13, """Missing newline before ")""""), ).isFormattedAs(formattedCode) } @@ -148,7 +148,7 @@ class ParameterListWrappingRuleTest { """.trimIndent() @Suppress("ktlint:argument-list-wrapping", "ktlint:max-line-length") parameterListWrappingRuleAssertThat(code) - .hasLintViolation(3, 13, "Parameter should be on a separate line (unless all parameters can fit a single line)") + .hasLintViolation(3, 13, "Parameter should start on a newline") .isFormattedAs(formattedCode) } @@ -171,9 +171,9 @@ class ParameterListWrappingRuleTest { parameterListWrappingRuleAssertThat(code) .withEditorConfigOverride(MAX_LINE_LENGTH_PROPERTY to 10) .hasLintViolations( - LintViolation(1, 7, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(1, 15, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(1, 23, "Parameter should be on a separate line (unless all parameters can fit a single line)"), + LintViolation(1, 7, "Parameter should start on a newline"), + LintViolation(1, 15, "Parameter should start on a newline"), + LintViolation(1, 23, "Parameter should start on a newline"), LintViolation(1, 29, """Missing newline before ")""""), ).isFormattedAs(formattedCode) } @@ -257,8 +257,8 @@ class ParameterListWrappingRuleTest { parameterListWrappingRuleAssertThat(code) .withEditorConfigOverride(MAX_LINE_LENGTH_PROPERTY to 10) .hasLintViolations( - LintViolation(2, 11, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(6, 19, "Parameter should be on a separate line (unless all parameters can fit a single line)"), + LintViolation(2, 11, "Parameter should start on a newline"), + LintViolation(6, 19, "Parameter should start on a newline"), LintViolation(6, 37, """Missing newline before ")""""), ).isFormattedAs(formattedCode) } @@ -288,7 +288,7 @@ class ParameterListWrappingRuleTest { """.trimIndent() @Suppress("ktlint:argument-list-wrapping", "ktlint:max-line-length") parameterListWrappingRuleAssertThat(code) - .hasLintViolation(2, 11, "Parameter should be on a separate line (unless all parameters can fit a single line)") + .hasLintViolation(2, 11, "Parameter should start on a newline") .isFormattedAs(formattedCode) } @@ -316,8 +316,8 @@ class ParameterListWrappingRuleTest { """.trimIndent() parameterListWrappingRuleAssertThat(code) .hasLintViolations( - LintViolation(4, 12, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(4, 25, "Parameter should be on a separate line (unless all parameters can fit a single line)"), + LintViolation(4, 12, "Parameter should start on a newline"), + LintViolation(4, 25, "Parameter should start on a newline"), LintViolation(5, 32, """Missing newline before ")""""), LintViolation(5, 41, """Missing newline before ")""""), ).isFormattedAs(formattedCode) @@ -344,12 +344,12 @@ class ParameterListWrappingRuleTest { parameterListWrappingRuleAssertThat(code) .withEditorConfigOverride(MAX_LINE_LENGTH_PROPERTY to 10) .hasLintViolations( - LintViolation(1, 11, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(1, 26, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(1, 48, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(1, 55, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(1, 68, "Parameter should be on a separate line (unless all parameters can fit a single line)"), - LintViolation(1, 90, "Parameter should be on a separate line (unless all parameters can fit a single line)"), + LintViolation(1, 11, "Parameter should start on a newline"), + LintViolation(1, 26, "Parameter should start on a newline"), + LintViolation(1, 48, "Parameter should start on a newline"), + LintViolation(1, 55, "Parameter should start on a newline"), + LintViolation(1, 68, "Parameter should start on a newline"), + LintViolation(1, 90, "Parameter should start on a newline"), LintViolation(1, 117, "Missing newline before \")\""), LintViolation(1, 126, "Missing newline before \")\""), ).isFormattedAs(formattedCode) @@ -514,4 +514,47 @@ class ParameterListWrappingRuleTest { LintViolation(1, 95, """Missing newline before ")""""), ).isFormattedAs(formattedCode) } + + @Nested + inner class `Given a single line function signature with an annotated parameter` { + @Test + fun `Given ktlint_official code style`() { + val code = + """ + fun foo(a: Int, @Bar bar: String, b: Int) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo( + a: Int, + @Bar bar: String, + b: Int + ) = "some-result" + """.trimIndent() + parameterListWrappingRuleAssertThat(code) + .withEditorConfigOverride(CODE_STYLE_PROPERTY to ktlint_official) + .hasLintViolations( + LintViolation(1, 9, "Parameter should start on a newline"), + LintViolation(1, 17, "Parameter should start on a newline"), + LintViolation(1, 35, "Parameter should start on a newline"), + LintViolation(1, 41, "Missing newline before \")\""), + ).isFormattedAs(formattedCode) + } + + @ParameterizedTest(name = "Code style: {0}") + @EnumSource( + value = CodeStyleValue::class, + mode = EnumSource.Mode.EXCLUDE, + names = ["ktlint_official"], + ) + fun `Given non-ktlint_official code style`(codeStyle: CodeStyleValue) { + val code = + """ + fun foo(a: Int, @Bar bar: String, b: Int) = "some-result" + """.trimIndent() + parameterListWrappingRuleAssertThat(code) + .withEditorConfigOverride(CODE_STYLE_PROPERTY to codeStyle) + .hasNoLintViolations() + } + } }