diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRule.kt new file mode 100644 index 0000000000..dda6839a61 --- /dev/null +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRule.kt @@ -0,0 +1,70 @@ +package com.github.shyiko.ktlint.ruleset.standard + +import com.github.shyiko.ktlint.core.Rule +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +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.com.intellij.psi.tree.TokenSet +import org.jetbrains.kotlin.lexer.KtTokens.DOT +import org.jetbrains.kotlin.lexer.KtTokens.ELVIS +import org.jetbrains.kotlin.lexer.KtTokens.SAFE_ACCESS +import org.jetbrains.kotlin.psi.psiUtil.nextLeaf +import org.jetbrains.kotlin.psi.psiUtil.prevLeaf + +class ChainWrappingRule : Rule("chain-wrapping") { + + // *,+,-,/,% position differ in + // https://github.com/yole/kotlin-style-guide/issues/9 + // https://android.github.io/kotlin-guides/style.html#where-to-break + private val sameLineTokens = TokenSet.EMPTY // TokenSet.create(MUL, PLUS, MINUS, DIV, PERC) + private val nextLineTokens = TokenSet.create(DOT, SAFE_ACCESS, ELVIS) + private val noSpaceAroundTokens = TokenSet.create(DOT, SAFE_ACCESS) + + override fun visit(node: ASTNode, autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { + /* + org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement (DOT) | "." + org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl (WHITE_SPACE) | "\n " + org.jetbrains.kotlin.psi.KtCallExpression (CALL_EXPRESSION) + */ + val elementType = node.elementType + if (nextLineTokens.contains(elementType)) { + val nextLeaf = node.psi.nextLeaf(true) + if (nextLeaf is PsiWhiteSpaceImpl && nextLeaf.textContains('\n')) { + emit(node.startOffset, "Line must not end with \"${node.text}\"", true) + if (autoCorrect) { + val prevLeaf = node.psi.prevLeaf(true) + if (prevLeaf is PsiWhiteSpaceImpl) { + prevLeaf.rawReplaceWithText(nextLeaf.text) + } else { + (node.psi as LeafPsiElement).rawInsertBeforeMe(PsiWhiteSpaceImpl(nextLeaf.text)) + } + if (noSpaceAroundTokens.contains(elementType)) { + nextLeaf.node.treeParent.removeChild(nextLeaf.node) + } else { + nextLeaf.rawReplaceWithText(" ") + } + } + } + } else + if (sameLineTokens.contains(elementType)) { + val prevLeaf = node.psi.prevLeaf(true) + if (prevLeaf is PsiWhiteSpaceImpl && prevLeaf.textContains('\n')) { + emit(node.startOffset, "Line must not begin with \"${node.text}\"", true) + if (autoCorrect) { + val nextLeaf = node.psi.nextLeaf(true) + if (nextLeaf is PsiWhiteSpaceImpl) { + nextLeaf.rawReplaceWithText(prevLeaf.text) + } else { + (node.psi as LeafPsiElement).rawInsertAfterMe(PsiWhiteSpaceImpl(prevLeaf.text)) + } + if (noSpaceAroundTokens.contains(elementType)) { + prevLeaf.node.treeParent.removeChild(prevLeaf.node) + } else { + prevLeaf.rawReplaceWithText(" ") + } + } + } + } + } +} diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt index 6c13a7a3b4..aa57a54f11 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt @@ -6,6 +6,7 @@ import com.github.shyiko.ktlint.core.RuleSetProvider class StandardRuleSetProvider : RuleSetProvider { override fun get(): RuleSet = RuleSet("standard", + ChainWrappingRule(), FinalNewlineRule(), // disabled until it's clear how to reconcile difference in Intellij & Android Studio import layout // ImportOrderingRule(), diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRuleTest.kt new file mode 100644 index 0000000000..b96cf10bfe --- /dev/null +++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRuleTest.kt @@ -0,0 +1,14 @@ +package com.github.shyiko.ktlint.ruleset.standard + +import org.testng.annotations.Test + +class ChainWrappingRuleTest { + + @Test + fun testLint() = + testLintUsingResource(ChainWrappingRule()) + + @Test + fun testFormat() = + testFormatUsingResource(ChainWrappingRule()) +} diff --git a/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format-expected.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format-expected.kt.spec new file mode 100644 index 0000000000..1724690262 --- /dev/null +++ b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format-expected.kt.spec @@ -0,0 +1,9 @@ +fun main() { + val anchor = owner.firstChild!! + .siblings(forward = true) + .dropWhile { it is PsiComment || it is PsiWhiteSpace } + val s = foo() + ?: bar + val s = foo() + ?.bar +} diff --git a/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format.kt.spec new file mode 100644 index 0000000000..68b0dc3f5e --- /dev/null +++ b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format.kt.spec @@ -0,0 +1,9 @@ +fun main() { + val anchor = owner.firstChild!!. + siblings(forward = true). + dropWhile { it is PsiComment || it is PsiWhiteSpace } + val s = foo() ?: + bar + val s = foo()?. + bar +} diff --git a/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/lint.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/lint.kt.spec new file mode 100644 index 0000000000..f6a1f0c807 --- /dev/null +++ b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/lint.kt.spec @@ -0,0 +1,17 @@ +fun main() { + val anchor = owner.firstChild!!. + siblings(forward = true). + dropWhile { it is PsiComment || it is PsiWhiteSpace } + val s = foo() ?: + bar + val s = foo()?. + bar +// val s = "foo" +// + "bar" +} + +// expect +// 2:36:Line must not end with "." +// 3:33:Line must not end with "." +// 5:19:Line must not end with "?:" +// 7:18:Line must not end with "?."