Skip to content

Commit

Permalink
Introduce SpacingAroundDoubleColonRule (#722)
Browse files Browse the repository at this point in the history
  • Loading branch information
Roman Zavarnitsyn authored Apr 22, 2020
1 parent b952857 commit ff527eb
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ExperimentalRuleSetProvider : RuleSetProvider {
MultiLineIfElseRule(),
NoEmptyFirstLineInMethodBlockRule(),
PackageNameRule(),
EnumEntryNameCaseRule()
EnumEntryNameCaseRule(),
SpacingAroundDoubleColonRule()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.pinterest.ktlint.ruleset.experimental

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.CALLABLE_REFERENCE_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.CLASS_LITERAL_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.COLONCOLON
import com.pinterest.ktlint.core.ast.isPartOf
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
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

class SpacingAroundDoubleColonRule : Rule("double-colon-spacing") {

override fun visit(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.elementType == COLONCOLON) {
val prevLeaf = node.prevLeaf()
val nextLeaf = node.nextLeaf()

var removeSingleWhiteSpace = false
val spacingBefore = when {
node.isPartOf(CLASS_LITERAL_EXPRESSION) && prevLeaf is PsiWhiteSpace -> true // Clazz::class
node.isPartOf(CALLABLE_REFERENCE_EXPRESSION) && prevLeaf is PsiWhiteSpace -> // String::length, ::isOdd
if (node.treePrev == null) { // compose(length, ::isOdd), val predicate = ::isOdd
removeSingleWhiteSpace = true
!prevLeaf.textContains('\n') && prevLeaf.psi.textLength > 1
} else { // String::length, List<String>::isEmpty
!prevLeaf.textContains('\n')
}
else -> false
}

val spacingAfter = nextLeaf is PsiWhiteSpace
when {
spacingBefore && spacingAfter -> {
emit(node.startOffset, "Unexpected spacing around \"${node.text}\"", true)
if (autoCorrect) {
prevLeaf!!.removeSelf(removeSingleWhiteSpace)
nextLeaf!!.treeParent.removeChild(nextLeaf)
}
}
spacingBefore -> {
emit(prevLeaf!!.startOffset, "Unexpected spacing before \"${node.text}\"", true)
if (autoCorrect) {
prevLeaf.removeSelf(removeSingleWhiteSpace)
}
}
spacingAfter -> {
emit(nextLeaf!!.startOffset, "Unexpected spacing after \"${node.text}\"", true)
if (autoCorrect) {
nextLeaf.treeParent.removeChild(nextLeaf)
}
}
}
}
}

private fun ASTNode.removeSelf(removeSingleWhiteSpace: Boolean) {
if (removeSingleWhiteSpace) {
(this as LeafPsiElement).rawReplaceWithText(text.substring(0, textLength - 1))
} else {
treeParent.removeChild(this)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.pinterest.ktlint.ruleset.experimental

import com.pinterest.ktlint.test.diffFileFormat
import com.pinterest.ktlint.test.diffFileLint
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

class SpacingAroundDoubleColonRuleTest {

@Test
fun testLint() {
assertThat(
SpacingAroundDoubleColonRule().diffFileLint("spec/spacing-around-double-colon/lint.kt.spec")
).isEmpty()
}

@Test
fun testFormat() {
assertThat(
SpacingAroundDoubleColonRule().diffFileFormat(
"spec/spacing-around-double-colon/format.kt.spec",
"spec/spacing-around-double-colon/format-expected.kt.spec"
)
).isEmpty()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
fun main() {
val a = AClass::class
val b = BClass::class
val c = CClass::class
val d = DClass::class
val e = EClass::class
val f = FClass::class

fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"
val predicateA: (String) -> Boolean = ::isOdd
val predicateB: (String) -> Boolean = ::isOdd
val predicateC: (String) -> Boolean = ::isOdd
val predicateD: (String) -> Boolean = ::isOdd
val predicateE: (String) -> Boolean =
::isOdd
val predicateF: (String) -> Boolean = ::isOdd

if (true == ::isOdd.invoke("")) {
// do stuff
}

val isEmptyStringList: List<String>.() -> Boolean = List<String>::isEmpty
val isNotEmptyStringList: List<String>.() -> Boolean = List<String>::isNotEmpty

function(::Foo)
function(
::Foo
)

items.filter(::isEven)
.map(String::length)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
fun main() {
val a = AClass::class
val b = BClass ::class
val c = CClass:: class
val d = DClass :: class
val e = EClass::
class
val f = FClass :: class

fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"
val predicateA: (String) -> Boolean = :: isOdd
val predicateB: (String) -> Boolean = ::isOdd
val predicateC: (String) -> Boolean = :: isOdd
val predicateD: (String) -> Boolean = ::isOdd
val predicateE: (String) -> Boolean =
::isOdd
val predicateF: (String) -> Boolean = ::
isOdd

if (true == ::isOdd.invoke("")) {
// do stuff
}

val isEmptyStringList: List<String>.() -> Boolean = List<String> :: isEmpty
val isNotEmptyStringList: List<String>.() -> Boolean = List<String>::isNotEmpty

function(::Foo)
function(
::
Foo
)

items.filter(::isEven)
.map(String ::length)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
fun main() {
val a = AClass::class
val b = BClass ::class
val c = CClass:: class
val d = DClass :: class
val e = EClass::
class

fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"
val predicateA: (String) -> Boolean = :: isOdd
val predicateB: (String) -> Boolean = ::isOdd
val predicateC: (String) -> Boolean = :: isOdd
val predicateD: (String) -> Boolean = ::isOdd
val predicateE: (String) -> Boolean =
::isOdd
val predicateF: (String) -> Boolean = ::
isOdd

if (true == ::isOdd.invoke("")) {
// do stuff
}

val isEmptyStringList: List<String>.() -> Boolean = List<String> :: isEmpty
val isNotEmptyStringList: List<String>.() -> Boolean = List<String>::isNotEmpty

function(::Foo)
function(
::
Foo
)

items.filter(::isEven)
.map(String ::length)
}

// expect
// 3:19:Unexpected spacing before "::"
// 4:21:Unexpected spacing after "::"
// 5:20:Unexpected spacing around "::"
// 6:21:Unexpected spacing after "::"
// 10:45:Unexpected spacing after "::"
// 11:42:Unexpected spacing before "::"
// 12:44:Unexpected spacing around "::"
// 16:45:Unexpected spacing after "::"
// 23:70:Unexpected spacing around "::"
// 28:11:Unexpected spacing after "::"
// 33:20:Unexpected spacing before "::"

0 comments on commit ff527eb

Please sign in to comment.