diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt index feacfc439d..87ddecddf4 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt @@ -12,6 +12,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.EXCLEQ import com.pinterest.ktlint.rule.engine.core.api.ElementType.EXCLEQEQEQ import com.pinterest.ktlint.rule.engine.core.api.ElementType.GT import com.pinterest.ktlint.rule.engine.core.api.ElementType.GTEQ +import com.pinterest.ktlint.rule.engine.core.api.ElementType.IDENTIFIER import com.pinterest.ktlint.rule.engine.core.api.ElementType.LT import com.pinterest.ktlint.rule.engine.core.api.ElementType.LTEQ import com.pinterest.ktlint.rule.engine.core.api.ElementType.MINUS @@ -42,31 +43,42 @@ import org.jetbrains.kotlin.psi.KtPrefixExpression @SinceKtlint("0.1", STABLE) public class SpacingAroundOperatorsRule : StandardRule("op-spacing") { - private val tokenSet = - TokenSet.create( - MUL, PLUS, MINUS, DIV, PERC, LT, GT, LTEQ, GTEQ, EQEQEQ, EXCLEQEQEQ, EQEQ, - EXCLEQ, ANDAND, OROR, ELVIS, EQ, MULTEQ, DIVEQ, PERCEQ, PLUSEQ, MINUSEQ, ARROW, - ) - override fun beforeVisitChildNodes( node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, ) { - if (tokenSet.contains(node.elementType) && - node.isNotUnaryOperator() && - isNotSpreadOperator(node) && - isNotImport(node) + if (node.isUnaryOperator()) { + // Allow: + // val foo = -1 + return + } + + if (node.isSpreadOperator()) { + // Allow: + // foo(*array) + return + } + + if (node.isImport()) { + // Allow: + // import * + return + } + + if ((node.elementType == LT || node.elementType == GT || node.elementType == MUL) && + node.treeParent.elementType != OPERATION_REFERENCE + ) { + // Allow: + // fun foo(...) + // class Foo { ... } + // Foo<*> + return + } + + if (node.elementType in OPERATORS || + (node.elementType == IDENTIFIER && node.treeParent.elementType == OPERATION_REFERENCE) ) { - if ((node.elementType == LT || node.elementType == GT || node.elementType == MUL) && - node.treeParent.elementType != OPERATION_REFERENCE - ) { - // Do not format parameter types like: - // fun foo(...) - // class Foo { ... } - // Foo<*> - return - } val spacingBefore = node.prevLeaf() is PsiWhiteSpace val spacingAfter = node.nextLeaf() is PsiWhiteSpace when { @@ -84,7 +96,7 @@ public class SpacingAroundOperatorsRule : StandardRule("op-spacing") { } } !spacingAfter -> { - emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true) + emit(node.startOffset + node.textLength, "Missing spacing after \"${node.text}\"", true) if (autoCorrect) { node.upsertWhitespaceAfterMe(" ") } @@ -93,15 +105,44 @@ public class SpacingAroundOperatorsRule : StandardRule("op-spacing") { } } - private fun ASTNode.isNotUnaryOperator() = !isPartOf(KtPrefixExpression::class) + private fun ASTNode.isUnaryOperator() = isPartOf(KtPrefixExpression::class) - private fun isNotSpreadOperator(node: ASTNode) = + private fun ASTNode.isSpreadOperator() = // fn(*array) - !(node.elementType == MUL && node.treeParent.elementType == VALUE_ARGUMENT) + elementType == MUL && treeParent.elementType == VALUE_ARGUMENT - private fun isNotImport(node: ASTNode) = + private fun ASTNode.isImport() = // import * - !node.isPartOf(KtImportDirective::class) + isPartOf(KtImportDirective::class) + + private companion object { + private val OPERATORS = + TokenSet.create( + ANDAND, + ARROW, + DIV, + DIVEQ, + ELVIS, + EQ, + EQEQ, + EQEQEQ, + EXCLEQ, + EXCLEQEQEQ, + GT, + GTEQ, + LT, + LTEQ, + MINUS, + MINUSEQ, + MUL, + MULTEQ, + OROR, + PERC, + PERCEQ, + PLUS, + PLUSEQ, + ) + } } public val SPACING_AROUND_OPERATORS_RULE_ID: RuleId = SpacingAroundOperatorsRule().ruleId diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRuleTest.kt index fac21c9895..86da5807bc 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRuleTest.kt @@ -47,7 +47,7 @@ class SpacingAroundOperatorsRuleTest { .hasLintViolations( LintViolation(1, 13, "Missing spacing around \"${operator}\""), LintViolation(2, 13, "Missing spacing before \"${operator}\""), - LintViolation(3, 15, "Missing spacing after \"${operator}\""), + LintViolation(3, 14 + operator.length, "Missing spacing after \"${operator}\""), ).isFormattedAs(formattedCode) } @@ -248,10 +248,10 @@ class SpacingAroundOperatorsRuleTest { """.trimIndent() spacingAroundOperatorsRuleAssertThat(code) .hasLintViolations( - LintViolation(3, 8, "Missing spacing after \"+=\""), - LintViolation(4, 8, "Missing spacing after \"-=\""), - LintViolation(5, 8, "Missing spacing after \"/=\""), - LintViolation(6, 8, "Missing spacing after \"*=\""), + LintViolation(3, 9, "Missing spacing after \"+=\""), + LintViolation(4, 9, "Missing spacing after \"-=\""), + LintViolation(5, 9, "Missing spacing after \"/=\""), + LintViolation(6, 9, "Missing spacing after \"*=\""), ).isFormattedAs(formattedCode) } } @@ -285,4 +285,33 @@ class SpacingAroundOperatorsRuleTest { """.trimIndent() spacingAroundOperatorsRuleAssertThat(code).hasNoLintViolations() } + + @Test + fun `Given a custom DSL`() { + val code = + """ + fun foo() { + every { foo() }returns(bar)andThen(baz) + every { foo() }returns (bar)andThen (baz) + every { foo() } returns(bar) andThen(baz) + } + """.trimIndent() + val formattedCode = + """ + fun foo() { + every { foo() } returns (bar) andThen (baz) + every { foo() } returns (bar) andThen (baz) + every { foo() } returns (bar) andThen (baz) + } + """.trimIndent() + spacingAroundOperatorsRuleAssertThat(code) + .hasLintViolations( + LintViolation(2, 20, "Missing spacing around \"returns\""), + LintViolation(2, 32, "Missing spacing around \"andThen\""), + LintViolation(3, 20, "Missing spacing before \"returns\""), + LintViolation(3, 33, "Missing spacing before \"andThen\""), + LintViolation(4, 28, "Missing spacing after \"returns\""), + LintViolation(4, 41, "Missing spacing after \"andThen\""), + ).isFormattedAs(formattedCode) + } }