diff --git a/CHANGELOG.md b/CHANGELOG.md index cf39fcdf51..8094a8cc98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,21 +2,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). -## Unreleased (0.46.0) - -### API Changes & RuleSet providers - -### Added -- Add experimental rule for unexpected spacing between function name and opening parenthesis (`spacing-between-function-name-and-opening-parenthesis`) ([#1341](https://github.com/pinterest/ktlint/issues/1341)) - -### Fixed - -### Changed - -### Removed - - -## Unreleased (0.45.0) +## Unreleased ### API Changes & RuleSet providers @@ -27,16 +13,19 @@ If you are not an API user nor a RuleSet provider, then you can safely skip this An AssertJ style API for testing KtLint rules ([#1444](https://github.com/pinterest/ktlint/issues/1444)) has been added. Usage of this API is encouraged in favor of using the old RuleExtension API. For more information, see [KtLintAssertThat API]( https://github.com/pinterest/ktlint/blob/master/ktlint-test/README.MD) ### Added +- Add experimental rule for unexpected spacing between function name and opening parenthesis (`spacing-between-function-name-and-opening-parenthesis`) ([#1341](https://github.com/pinterest/ktlint/issues/1341)) +- Add experimental rule for unexpected spacing in the parameter list (`parameter-list-spacing`) ([#1341](https://github.com/pinterest/ktlint/issues/1341)) +- Do not add a space after the typealias name (`type-parameter-list-spacing`) ([#1435](https://github.com/pinterest/ktlint/issues/1435)) ### Fixed -- Do not add a space after the typealias name (`type-parameter-list-spacing`) ([#1435](https://github.com/pinterest/ktlint/issues/1435)) ### Changed - * Set Kotlin development version to `1.6.21` and Kotlin version to `1.6.21`. ### Removed +### Removed + ## [0.45.2] - 2022-04-06 ### Fixed diff --git a/README.md b/README.md index a2fe8ccca1..e88f1c1294 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ by passing the `--experimental` flag to `ktlint`. - `experimental:multiline-if-else`: Braces required for multiline if/else statements - `experimental:no-empty-first-line-in-method-block`: No leading empty lines in method blocks - `experimental:package-name`: No underscores in package names +- `experimental:parameter-list-spacing`: Consistent spacing inside the parameter list - `experimental:unnecessary-parentheses-before-trailing-lambda`: An empty parentheses block before a lambda is redundant. For example `some-string".count() { it == '-' }` ### Spacing diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt index 08e37f945a..2801acc3ae 100644 --- a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt @@ -31,6 +31,7 @@ public class ExperimentalRuleSetProvider : RuleSetProvider { ModifierListSpacingRule(), CommentWrappingRule(), KdocWrappingRule(), - SpacingBetweenFunctionNameAndOpeningParenthesisRule() + SpacingBetweenFunctionNameAndOpeningParenthesisRule(), + ParameterListSpacingRule() ) } diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ParameterListSpacingRule.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ParameterListSpacingRule.kt new file mode 100644 index 0000000000..72caf43100 --- /dev/null +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ParameterListSpacingRule.kt @@ -0,0 +1,242 @@ +package com.pinterest.ktlint.ruleset.experimental + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION_ENTRY +import com.pinterest.ktlint.core.ast.ElementType.COLON +import com.pinterest.ktlint.core.ast.ElementType.COMMA +import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST +import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER +import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST +import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE +import com.pinterest.ktlint.core.ast.children +import com.pinterest.ktlint.core.ast.nextCodeSibling +import com.pinterest.ktlint.core.ast.nextLeaf +import com.pinterest.ktlint.core.ast.nextSibling +import com.pinterest.ktlint.core.ast.prevCodeSibling +import com.pinterest.ktlint.core.ast.prevLeaf +import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement + +/** + * Ensures consistent spacing inside the parameter list. This rule partly overlaps with other rules like spacing around + * comma's and colons. However, it does have a more complete view on the higher concept of the parameter-list without + * interfering of the parameter-list-wrapping rule. + */ +public class ParameterListSpacingRule : Rule("parameter-list-spacing") { + override fun visit( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + ) { + if (node.elementType == VALUE_PARAMETER_LIST) { + visitValueParameterList(node, emit, autoCorrect) + } + } + + private fun visitValueParameterList( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + require(node.elementType == VALUE_PARAMETER_LIST) + val countValueParameters = + node + .children() + .count { it.elementType == VALUE_PARAMETER } + var valueParameterCount = 0 + val iterator = + node + .children() + // Store elements in list before changing them as otherwise only one element is being changed + .toList() + .iterator() + while (iterator.hasNext()) { + val el = iterator.next() + when (el.elementType) { + WHITE_SPACE -> { + if (countValueParameters == 0) { + removeUnexpectedWhiteSpace(el, emit, autoCorrect) + } else if (valueParameterCount == 0 && el.isNotIndent()) { + // whitespace before first parameter + removeUnexpectedWhiteSpace(el, emit, autoCorrect) + } else if (valueParameterCount == countValueParameters && el.isNotIndent()) { + // whitespace after the last parameter + removeUnexpectedWhiteSpace(el, emit, autoCorrect) + } else if (el.nextCodeSibling()?.elementType == COMMA) { + // No whitespace between parameter name and comma allowed + removeUnexpectedWhiteSpace(el, emit, autoCorrect) + } else if (el.elementType == WHITE_SPACE && el.isNotIndent() && el.isNotSingleSpace()) { + require(el.prevCodeSibling()?.elementType == COMMA) + replaceWithSingleSpace(el, emit, autoCorrect) + } + } + COMMA -> { + // Comma must be followed by whitespace + val nextSibling = + el + .nextSibling { true } + ?.elementType + if (nextSibling != WHITE_SPACE) { + addMissingWhiteSpaceAfterMe(el, emit, autoCorrect) + } + } + VALUE_PARAMETER -> { + valueParameterCount += 1 + visitValueParameter(el, emit, autoCorrect) + } + } + } + } + + private fun visitValueParameter( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + visitModifierList(node, emit, autoCorrect) + removeWhiteSpaceBetweenParameterIdentifierAndColon(node, emit, autoCorrect) + fixWhiteSpaceAfterColonInParameter(node, emit, autoCorrect) + } + + private fun visitModifierList( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + val modifierList = node.findChildByType(MODIFIER_LIST) ?: return + removeWhiteSpaceBetweenModifiersInList(modifierList, emit, autoCorrect) + removeWhiteSpaceBetweenModifierListAndParameterIdentifier(modifierList, emit, autoCorrect) + } + + private fun removeWhiteSpaceBetweenModifiersInList( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + require(node.elementType == MODIFIER_LIST) + node + .children() + .filter { it.elementType == WHITE_SPACE } + // Store elements in list before changing them as otherwise only the first whitespace is being changed + .toList() + .forEach { visitWhiteSpaceAfterModifier(it, emit, autoCorrect) } + } + + private fun visitWhiteSpaceAfterModifier( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + node + .takeUnless { + // Ignore when the modifier is an annotation which is placed on a separate line + it.isIndent() && it.getPrecedingModifier()?.elementType == ANNOTATION_ENTRY + } + ?.takeIf { it.isNotSingleSpace() } + ?.let { replaceWithSingleSpace(it, emit, autoCorrect) } + } + + private fun removeWhiteSpaceBetweenModifierListAndParameterIdentifier( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + require(node.elementType == MODIFIER_LIST) + node + .nextSibling { true } + ?.takeIf { it.elementType == WHITE_SPACE } + ?.let { visitWhiteSpaceAfterModifier(it, emit, autoCorrect) } + } + + private fun removeWhiteSpaceBetweenParameterIdentifierAndColon( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + node + .findChildByType(COLON) + ?.prevLeaf() + ?.takeIf { it.elementType == WHITE_SPACE } + ?.let { whiteSpaceBeforeColon -> + removeUnexpectedWhiteSpace(whiteSpaceBeforeColon, emit, autoCorrect) + } + } + + private fun fixWhiteSpaceAfterColonInParameter( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + val colonNode = node.findChildByType(COLON) ?: return + colonNode + .nextLeaf() + ?.takeIf { it.elementType == WHITE_SPACE } + .let { whiteSpaceAfterColon -> + if (whiteSpaceAfterColon == null) { + addMissingWhiteSpaceAfterMe(colonNode, emit, autoCorrect) + } else if (whiteSpaceAfterColon.isIndent() || whiteSpaceAfterColon.isNotSingleSpace()) { + replaceWithSingleSpace(whiteSpaceAfterColon, emit, autoCorrect) + } + } + } + + private fun addMissingWhiteSpaceAfterMe( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + require(node.elementType == COLON || node.elementType == COMMA) + emit(node.startOffset, "Whitespace after '${node.text}' is missing", true) + if (autoCorrect) { + (node as LeafElement).upsertWhitespaceAfterMe(" ") + } + } + + private fun ASTNode.isNotIndent(): Boolean = !isIndent() + + private fun ASTNode.isIndent(): Boolean { + require(elementType == WHITE_SPACE) + return text.startsWith("\n") + } + + private fun ASTNode.isNotSingleSpace(): Boolean { + require(elementType == WHITE_SPACE) + return text != " " + } + + private fun removeUnexpectedWhiteSpace( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + emit(node.startOffset, "Unexpected whitespace", true) + if (autoCorrect) { + (node as LeafElement).rawRemove() + } + } + + private fun replaceWithSingleSpace( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + emit(node.startOffset, "Expected a single space", true) + if (autoCorrect) { + (node as LeafPsiElement).replaceWithText(" ") + } + } + + private fun ASTNode.getPrecedingModifier(): ASTNode? { + return prevCodeSibling() + ?.let { prevCodeSibling -> + if (prevCodeSibling.elementType == MODIFIER_LIST) { + prevCodeSibling.lastChildNode + } else { + require(prevCodeSibling.treeParent.elementType == MODIFIER_LIST) + prevCodeSibling + } + } + } +} diff --git a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/ParameterListSpacingRuleTest.kt b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/ParameterListSpacingRuleTest.kt new file mode 100644 index 0000000000..ad43c048cc --- /dev/null +++ b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/ParameterListSpacingRuleTest.kt @@ -0,0 +1,406 @@ +package com.pinterest.ktlint.ruleset.experimental + +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThat +import com.pinterest.ktlint.test.LintViolation +import org.junit.jupiter.api.Test + +class ParameterListSpacingRuleTest { + private val parameterListSpacingRuleAssertThat = ParameterListSpacingRule().assertThat() + + @Test + fun `Given a function signature which does not contain redundant spaces then do no reformat`() { + val code = + """ + fun foo(a: Any, vararg b: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a function signature without parameters but with at least one space between the parenthesis then reformat`() { + val code = + """ + fun foo( ) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo() = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 9, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature without parameters but with at least one newline between the parenthesis then reformat`() { + val code = + """ + fun foo( + ) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo() = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 9, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with at least one space before the first parameter then reformat`() { + val code = + """ + fun foo( a: Any) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(a: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 9, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with a newline before the first parameter then do not reformat`() { + val code = + """ + fun foo( + a: Any + ) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a function signature with at least one space after the last parameter then reformat`() { + val code = + """ + fun foo(a: Any ) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(a: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 15, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with a newline after the last parameter then do not reformat`() { + val code = + """ + fun foo( + a: Any + ) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a function signature with at least one space between the parameter name and the colon then reformat`() { + val code = + """ + fun foo(a : Any) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(a: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 10, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with at least one newline between the parameter name and the colon then reformat`() { + val code = + """ + fun foo(a + : Any) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(a: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 10, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature without space between the colon and the parameter type then reformat`() { + val code = + """ + fun foo(a:Any) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(a: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 10, "Whitespace after ':' is missing") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with too many spaces between the colon and the parameter type then reformat`() { + val code = + """ + fun foo(a:${TOO_MANY_SPACES}Any) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(a: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 11, "Expected a single space") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with a new line between the colon and the parameter type then reformat`() { + val code = + """ + fun foo(a: + Any) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(a: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 11, "Expected a single space") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with too many spaces between the modifier list and parameter name then reformat`() { + val code = + """ + fun foo1(vararg${TOO_MANY_SPACES}a: Any) = "some-result" + inline fun foo2(noinline${TOO_MANY_SPACES}bar: () -> Unit) { + bar() + } + inline fun foo3(crossinline${TOO_MANY_SPACES}bar: () -> Unit) { + bar() + } + """.trimIndent() + val formattedCode = + """ + fun foo1(vararg a: Any) = "some-result" + inline fun foo2(noinline bar: () -> Unit) { + bar() + } + inline fun foo3(crossinline bar: () -> Unit) { + bar() + } + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolations( + LintViolation(1, 16, "Expected a single space"), + LintViolation(2, 25, "Expected a single space"), + LintViolation(5, 28, "Expected a single space") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with at least one newline between the modifier list and parameter name then reformat`() { + val code = + """ + fun foo1(vararg + a: Any) = "some-result" + inline fun foo2(noinline + bar: () -> Unit) { + bar() + } + inline fun foo3(crossinline + bar: () -> Unit) { + bar() + } + """.trimIndent() + val formattedCode = + """ + fun foo1(vararg a: Any) = "some-result" + inline fun foo2(noinline bar: () -> Unit) { + bar() + } + inline fun foo3(crossinline bar: () -> Unit) { + bar() + } + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolations( + LintViolation(1, 16, "Expected a single space"), + LintViolation(3, 25, "Expected a single space"), + LintViolation(7, 28, "Expected a single space") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with too many spaces between the modifiers in the modifier list then reformat`() { + val code = + """ + // The code example below not make sense. Its importance is that modifier list can contain multiple elements + fun foo(vararg noinline crossinline a: Any) = "some-result" + """.trimIndent() + val formattedCode = + """ + // The code example below not make sense. Its importance is that modifier list can contain multiple elements + fun foo(vararg noinline crossinline a: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolations( + LintViolation(2, 15, "Expected a single space"), + LintViolation(2, 25, "Expected a single space"), + LintViolation(2, 38, "Expected a single space") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with newlines between the modifiers in the modifier list then reformat`() { + val code = + """ + // The code example below not make sense. Its importance is that modifier list can contain multiple elements + fun foo( + vararg + noinline + crossinline + a: Any + ) = "some-result" + """.trimIndent() + val formattedCode = + """ + // The code example below not make sense. Its importance is that modifier list can contain multiple elements + fun foo( + vararg noinline crossinline a: Any + ) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolations( + LintViolation(3, 11, "Expected a single space"), + LintViolation(4, 13, "Expected a single space"), + LintViolation(5, 16, "Expected a single space") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with an annotated parameter and annotations separated by newlines then do not reformat`() { + val code = + """ + fun foo( + @Bar1(value = "bar1") + @Bar2(value = "bar2") + a: Any + ) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a function signature with multiple parameters and at least one space before the comma separating the parameters then reformat`() { + val code = + """ + fun foo(a: Any${TOO_MANY_SPACES}, b: Any) = "some-result" + """.trimIndent() // ktlint-disable string-template + val formattedCode = + """ + fun foo(a: Any, b: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 15, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with multiple parameters and at least one newline before the comma separating the parameters then reformat`() { + val code = + """ + fun foo(a: Any + , b: Any) = "some-result" + """.trimIndent() // ktlint-disable string-template + val formattedCode = + """ + fun foo(a: Any, b: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 15, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with multiple parameters and no whitespace after the comma separating the parameters then reformat`() { + val code = + """ + fun foo(a: Any,b: Any) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(a: Any, b: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 15, "Whitespace after ',' is missing") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with multiple parameters and too many spaces after the comma separating the parameters then reformat`() { + val code = + """ + fun foo(a: Any,${TOO_MANY_SPACES}b: Any) = "some-result" + """.trimIndent() // ktlint-disable string-template + val formattedCode = + """ + fun foo(a: Any, b: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolation(1, 16, "Expected a single space") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a function signature with multiple parameters and a newline after the comma separating the parameters then do not reformat`() { + val code = + """ + fun foo( + a: Any, + b: Any + ) = "some-result" + """.trimIndent() // ktlint-disable string-template + parameterListSpacingRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a function signature with multiple spacing errors in the parameters then reformat all`() { + val code = + """ + fun foo(c :Any, d : Any, b : Any , vararg a : Any ) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(c: Any, d: Any, b: Any, vararg a: Any) = "some-result" + """.trimIndent() + parameterListSpacingRuleAssertThat(code) + .hasLintViolations( + LintViolation(1, 10, "Unexpected whitespace"), + LintViolation(1, 11, "Whitespace after ':' is missing"), + LintViolation(1, 18, "Unexpected whitespace"), + LintViolation(1, 20, "Expected a single space"), + LintViolation(1, 27, "Expected a single space"), + LintViolation(1, 31, "Unexpected whitespace"), + LintViolation(1, 33, "Expected a single space"), + LintViolation(1, 38, "Unexpected whitespace"), + LintViolation(1, 40, "Expected a single space"), + LintViolation(1, 48, "Expected a single space"), + LintViolation(1, 51, "Unexpected whitespace"), + LintViolation(1, 53, "Expected a single space"), + LintViolation(1, 58, "Unexpected whitespace") + ).isFormattedAs(formattedCode) + } + + private companion object { + const val TOO_MANY_SPACES = " " + } +}