diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a02554ba0..5cbd71949a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ An AssertJ style API for testing KtLint rules ([#1444](https://github.com/pinter - 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)) - Add experimental rule for incorrect spacing around the function return type (`function-return-type-spacing`) ([#1341](https://github.com/pinterest/ktlint/pull/1341)) +- Add experimental rule for unexpected spaces in a nullable type (`nullable-type-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)) - Add experimental rule for consistent spacing before the start of the function body (`function-start-of-body-spacing`) ([#1341](https://github.com/pinterest/ktlint/issues/1341)) diff --git a/README.md b/README.md index e12e844a87..8c430674bb 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ by passing the `--experimental` flag to `ktlint`. - `experimental:function-start-of-body-spacing`: Consistent spacing before start of function body - `experimental:function-type-reference-spacing`: Consistent spacing in the type reference before a function - `experimental:modifier-list-spacing`: Consistent spacing between modifiers in and after the last modifier in a modifier list +- `experimental:nullable-type-spacing`: No spaces in a nullable type - `experimental:spacing-around-angle-brackets`: No spaces around angle brackets - `experimental:spacing-between-declarations-with-annotations`: Declarations with annotations should be separated by a blank line - `experimental:spacing-between-declarations-with-comments`: Declarations with comments should be separated by a blank line 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 924df8fa2e..edf18fb356 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 @@ -34,6 +34,7 @@ public class ExperimentalRuleSetProvider : RuleSetProvider { SpacingBetweenFunctionNameAndOpeningParenthesisRule(), ParameterListSpacingRule(), FunctionReturnTypeSpacingRule(), - FunctionStartOfBodySpacingRule() + FunctionStartOfBodySpacingRule(), + NullableTypeSpacingRule() ) } diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/NullableTypeSpacingRule.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/NullableTypeSpacingRule.kt new file mode 100644 index 0000000000..2ed271aabe --- /dev/null +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/NullableTypeSpacingRule.kt @@ -0,0 +1,27 @@ +package com.pinterest.ktlint.ruleset.experimental + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.QUEST +import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE +import com.pinterest.ktlint.core.ast.prevLeaf +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement + +public class NullableTypeSpacingRule : Rule("nullable-type-spacing") { + override fun visit( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + ) { + node + .takeIf { node.elementType == QUEST } + ?.prevLeaf() + ?.takeIf { it.elementType == WHITE_SPACE } + ?.let { whiteSpaceBeforeQuest -> + emit(whiteSpaceBeforeQuest.startOffset, "Unexpected whitespace", true) + if (autoCorrect) { + (whiteSpaceBeforeQuest as LeafPsiElement).rawRemove() + } + } + } +} diff --git a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NullableTypeSpacingRuleTest.kt b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NullableTypeSpacingRuleTest.kt new file mode 100644 index 0000000000..fc70587050 --- /dev/null +++ b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NullableTypeSpacingRuleTest.kt @@ -0,0 +1,163 @@ +package com.pinterest.ktlint.ruleset.experimental + +import com.pinterest.ktlint.core.LintError +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThat +import com.pinterest.ktlint.test.format +import com.pinterest.ktlint.test.lint +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test + +class NullableTypeSpacingRuleTest { + private val nullableTypeSpacingRuleAssertThat = NullableTypeSpacingRule().assertThat() + + @Test + fun `Given a simple nullable type with a space before the quest then remove this space`() { + val code = + """ + val foo : String ? = null + """.trimIndent() + val formattedCode = + """ + val foo : String? = null + """.trimIndent() + Assertions.assertThat(NullableTypeSpacingRule().lint(code)).containsExactly( + LintError(1, 17, "nullable-type-spacing", "Unexpected whitespace") + ) + Assertions.assertThat(NullableTypeSpacingRule().format(code)).isEqualTo(formattedCode) + } + + @Test + fun `Given a non-nullable list of a nullable type with a space before the quest then remove this space`() { + val code = + """ + val foo : List = listOf(null) + """.trimIndent() + val formattedCode = + """ + val foo : List = listOf(null) + """.trimIndent() + nullableTypeSpacingRuleAssertThat(code) + .hasLintViolation(1, 22, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a nullable list of a non-nullable type with a space before the quest then remove this space`() { + val code = + """ + val foo : List ? = null + """.trimIndent() + val formattedCode = + """ + val foo : List? = null + """.trimIndent() + nullableTypeSpacingRuleAssertThat(code) + .hasLintViolation(1, 23, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a type receiver of nullable simple type with a space before the quest then remove this space`() { + val code = + """ + fun String ?.foo() = "some-result" + """.trimIndent() + val formattedCode = + """ + fun String?.foo() = "some-result" + """.trimIndent() + nullableTypeSpacingRuleAssertThat(code) + .hasLintViolation(1, 11, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a type receiver of a non-nullable list of a nullable type with a space before the quest then remove this space`() { + val code = + """ + fun List.foo() = "some-result" + """.trimIndent() + val formattedCode = + """ + fun List.foo() = "some-result" + """.trimIndent() + nullableTypeSpacingRuleAssertThat(code) + .hasLintViolation(1, 16, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a parameter of a nullable type with a space before the quest then remove this space`() { + val code = + """ + fun foo(string: String ?) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(string: String?) = "some-result" + """.trimIndent() + nullableTypeSpacingRuleAssertThat(code) + .hasLintViolation(1, 23, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a parameter of a list of a nullable type with a space before the quest then remove this space`() { + val code = + """ + fun foo(string: List) = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(string: List) = "some-result" + """.trimIndent() + nullableTypeSpacingRuleAssertThat(code) + .hasLintViolation(1, 28, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a nullable function return type with a space before the quest then remove this space`() { + val code = + """ + fun foo(): String ? = "some-result" + """.trimIndent() + val formattedCode = + """ + fun foo(): String? = "some-result" + """.trimIndent() + nullableTypeSpacingRuleAssertThat(code) + .hasLintViolation(1, 18, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a return type of a non-nullable list of a nullable type with a space before the quest then remove this space`() { + val code = + """ + fun foo(): List = listOf("some-result", null) + """.trimIndent() + val formattedCode = + """ + fun foo(): List = listOf("some-result", null) + """.trimIndent() + nullableTypeSpacingRuleAssertThat(code) + .hasLintViolation(1, 23, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a return type of a nullable list with a space before the quest then remove this space`() { + val code = + """ + fun foo(): List ? = null + """.trimIndent() + val formattedCode = + """ + fun foo(): List? = null + """.trimIndent() + nullableTypeSpacingRuleAssertThat(code) + .hasLintViolation(1, 24, "Unexpected whitespace") + .isFormattedAs(formattedCode) + } +}