Added parameter-list-wrapping rule
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/EditorConfig.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/EditorConfig.kt
new file mode 100644
index 0000000..0a213bf
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/EditorConfig.kt
@@ -0,0 +1,41 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.KtLint
+import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
+
+// http://editorconfig.org/
+internal data class EditorConfig(
+ val indentSize: Int,
+ val continuationIndentSize: Int,
+ val maxLineLength: Int,
+ val insertFinalNewline: Boolean?
+) {
+
+ companion object {
+
+ private const val DEFAULT_INDENT = 4
+ private const val DEFAULT_CONTINUATION_INDENT = 4
+
+ // https://android.github.io/kotlin-guides/style.html#line-wrapping
+ private const val ANDROID_MAX_LINE_LENGTH = 100
+
+ fun from(node: FileASTNode): EditorConfig {
+ val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
+ val indentSizeRaw = editorConfig.get("indent_size")
+ val indentSize = when {
+ indentSizeRaw?.toLowerCase() == "unset" -> -1
+ else -> indentSizeRaw?.toIntOrNull() ?: DEFAULT_INDENT
+ }
+ val continuationIndentSizeRaw = editorConfig.get("continuation_indent_size")
+ val continuationIndentSize = when {
+ continuationIndentSizeRaw?.toLowerCase() == "unset" -> -1
+ else -> continuationIndentSizeRaw?.toIntOrNull() ?: DEFAULT_CONTINUATION_INDENT
+ }
+ val android = node.getUserData(KtLint.ANDROID_USER_DATA_KEY)!!
+ val maxLineLength = editorConfig.get("max_line_length")?.toIntOrNull()
+ ?: if (android) ANDROID_MAX_LINE_LENGTH else -1
+ val insertFinalNewline = editorConfig.get("insert_final_newline")?.toBoolean()
+ return EditorConfig(indentSize, continuationIndentSize, maxLineLength, insertFinalNewline)
+ }
+ }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRule.kt
index b6c108c..15835fa 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRule.kt
@@ -1,17 +1,14 @@
package com.github.shyiko.ktlint.ruleset.standard
-import com.github.shyiko.ktlint.core.KtLint
import com.github.shyiko.ktlint.core.Rule
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
-import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
-import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
-import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtParameterList
import org.jetbrains.kotlin.psi.KtParenthesizedExpression
import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression
@@ -20,69 +17,40 @@
import org.jetbrains.kotlin.psi.KtSuperTypeListEntry
import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.KtValueArgumentList
-import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
class IndentationRule : Rule("indent") {
- companion object {
- // indentation size recommended by JetBrains
- private const val DEFAULT_INDENT = 4
- private const val DEFAULT_CONTINUATION_INDENT = 4
- }
+ private var indentSize = -1
+ private var continuationIndentSize = -1
- private var indent = -1
- private var continuationIndent = -1
-
- override fun visit(node: ASTNode, autoCorrect: Boolean,
- emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+ override fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ) {
if (node.elementType == KtStubElementTypes.FILE) {
- val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
- val indentSize = editorConfig.get("indent_size")
- val continuationIndentSize = editorConfig.get("continuation_indent_size")
- indent = indentSize?.toIntOrNull() ?: if (indentSize?.toLowerCase() == "unset") -1 else DEFAULT_INDENT
- continuationIndent = continuationIndentSize?.toIntOrNull() ?: DEFAULT_CONTINUATION_INDENT
+ val ec = EditorConfig.from(node as FileASTNode)
+ indentSize = ec.indentSize
+ continuationIndentSize = ec.continuationIndentSize
return
}
- if (indent <= 0 || continuationIndent <= 0) {
+ if (indentSize <= 0 || continuationIndentSize <= 0) {
return
}
if (node is PsiWhiteSpace && !node.isPartOf(PsiComment::class)) {
val lines = node.getText().split("\n")
if (lines.size > 1) {
var offset = node.startOffset + lines.first().length + 1
- val firstParameter = lazy {
- PsiTreeUtil.findChildOfType(
- node.getNonStrictParentOfType(KtParameterList::class.java),
- KtParameter::class.java
- )
- }
- val firstParameterColumn = lazy { firstParameter.value?.column ?: 0 }
- val previousIndent = calculatePreviousIndent(node)
- val expectedIndentSize = if (continuationIndent == indent || shouldUseContinuationIndent(node))
- continuationIndent else indent
+ val previousIndentSize = node.previousIndentSize()
+ val expectedIndentSize = if (continuationIndentSize == indentSize || shouldUseContinuationIndent(node))
+ continuationIndentSize else indentSize
lines.tail().forEach { line ->
- if (line.isNotEmpty() && (line.length - previousIndent) % expectedIndentSize != 0) {
- if (node.isPartOf(KtParameterList::class) &&
- firstParameterColumn.value != 0 &&
- (
- // is not the first parameter
- node.parent.node.findChildByType(KtStubElementTypes.VALUE_PARAMETER) !=
- firstParameter.value?.node ||
- // ... or is next to (
- firstParameter.value?.let { PsiTreeUtil.prevLeaf(it, true) }?.node
- ?.elementType == KtTokens.LPAR)
- ) {
- if (firstParameterColumn.value - 1 != line.length) {
- emit(offset, "Unexpected indentation (${line.length}) (" +
- "parameters should be either vertically aligned or " +
- "indented by the multiple of $indent" +
- ")", false)
- }
- } else {
+ if (line.isNotEmpty() && (line.length - previousIndentSize) % expectedIndentSize != 0) {
+ if (!node.isPartOf(KtParameterList::class)) { // parameter list wrapping enforced by ParameterListWrappingRule
emit(offset,
- "Unexpected indentation (${line.length - previousIndent}) " +
+ "Unexpected indentation (${line.length - previousIndentSize}) " +
"(it should be $expectedIndentSize)",
false)
}
@@ -93,67 +61,41 @@
}
}
- private val PsiElement.column: Int
- get() {
- var leaf = PsiTreeUtil.prevLeaf(this)
- var offsetToTheLeft = 0
- while (leaf != null) {
- if (leaf.node.elementType == KtTokens.WHITE_SPACE && leaf.textContains('\n')) {
- offsetToTheLeft += leaf.textLength - 1 - leaf.text.lastIndexOf('\n')
- break
- }
- offsetToTheLeft += leaf.textLength
- leaf = PsiTreeUtil.prevLeaf(leaf)
- }
- return offsetToTheLeft + 1
- }
-
private fun shouldUseContinuationIndent(node: PsiWhiteSpace): Boolean {
val parentNode = node.parent
val prevNode = node.getPrevSiblingIgnoringWhitespaceAndComments()?.node?.elementType
val nextNode = node.nextSibling?.node?.elementType
return (
- prevNode in KtTokens.ALL_ASSIGNMENTS
- || parentNode is KtSecondaryConstructor
- || nextNode == KtStubElementTypes.TYPE_REFERENCE
- || node.nextSibling is KtSuperTypeList
- || node.nextSibling is KtSuperTypeListEntry
- || node.nextSibling is KtTypeProjection
- || parentNode is KtValueArgumentList
- || parentNode is KtBinaryExpression
- || parentNode is KtDotQualifiedExpression
- || parentNode is KtSafeQualifiedExpression
- || parentNode is KtParenthesizedExpression
- )
+ prevNode in KtTokens.ALL_ASSIGNMENTS ||
+ parentNode is KtSecondaryConstructor ||
+ nextNode == KtStubElementTypes.TYPE_REFERENCE ||
+ node.nextSibling is KtSuperTypeList ||
+ node.nextSibling is KtSuperTypeListEntry ||
+ node.nextSibling is KtTypeProjection ||
+ parentNode is KtValueArgumentList ||
+ parentNode is KtBinaryExpression ||
+ parentNode is KtDotQualifiedExpression ||
+ parentNode is KtSafeQualifiedExpression ||
+ parentNode is KtParenthesizedExpression
+ )
}
- private fun calculatePreviousIndent(node: ASTNode): Int {
- val parentNode = node.treeParent?.psi
- var prevIndent = 0
+ private fun ASTNode.previousIndentSize(): Int {
+ val parentNode = this.treeParent?.psi
var prevSibling = parentNode
- var prevSpaceIsFound = false
- while (prevSibling != null && !prevSpaceIsFound) {
+ while (prevSibling != null) {
val nextNode = prevSibling.nextSibling?.node?.elementType
- if (prevSibling is PsiWhiteSpace
- && nextNode != KtStubElementTypes.TYPE_REFERENCE
- && nextNode != KtStubElementTypes.SUPER_TYPE_LIST
- && nextNode != KtNodeTypes.CONSTRUCTOR_DELEGATION_CALL) {
+ if (prevSibling is PsiWhiteSpace &&
+ nextNode != KtStubElementTypes.TYPE_REFERENCE &&
+ nextNode != KtStubElementTypes.SUPER_TYPE_LIST &&
+ nextNode != KtNodeTypes.CONSTRUCTOR_DELEGATION_CALL) {
val prevLines = prevSibling.text.split('\n')
if (prevLines.size > 1) {
- prevIndent = prevLines.last().length
- prevSpaceIsFound = true
+ return prevLines.last().length
}
}
- prevSibling = if (prevSpaceIsFound) {
- null
- } else {
- if (prevSibling.prevSibling != null) {
- prevSibling.prevSibling
- } else {
- prevSibling.parent
- }
- }
+ prevSibling = prevSibling.prevSibling ?: prevSibling.parent
}
- return prevIndent
+ return 0
}
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt
new file mode 100644
index 0000000..ad4af27
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt
@@ -0,0 +1,101 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.KtNodeTypes
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
+import org.jetbrains.kotlin.com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.psiUtil.children
+import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
+
+class ParameterListWrappingRule : Rule("parameter-list-wrapping") {
+
+ private var indentSize = -1
+ private var maxLineLength: Int = -1
+
+ override fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ) {
+ if (node.elementType == KtStubElementTypes.FILE) {
+ val ec = EditorConfig.from(node as FileASTNode)
+ indentSize = ec.indentSize
+ maxLineLength = ec.maxLineLength
+ return
+ }
+ if (node.elementType == KtStubElementTypes.VALUE_PARAMETER_LIST &&
+ // skip lambda parameters
+ node.treeParent?.elementType != KtNodeTypes.FUNCTION_LITERAL) {
+ // each parameter should be on a separate line if
+ // - at least one of the parameters is
+ // - maxLineLength exceeded (and separating parameters with \n would actually help)
+ // in addition, "(" and ")" must be on separates line if any of the parameters are (otherwise on the same)
+ val putParametersOnSeparateLines = node.textContains('\n')
+ // maxLineLength > 0 && node.lineLength() > maxLineLength
+ if (putParametersOnSeparateLines) {
+ // aiming for
+ // ... LPAR
+ // <LPAR line indent + indentSize> VALUE_PARAMETER...
+ // <LPAR line indent> RPAR
+ val indent = "\n" + node.psi.lineIndent()
+ val paramIndent = indent + " ".repeat(indentSize) // single indent as recommended by Jetbrains/Google
+ nextChild@ for (child in node.children()) {
+ when (child.elementType) {
+ KtStubElementTypes.VALUE_PARAMETER,
+ KtTokens.RPAR -> {
+ val prevLeaf = child.psi.prevLeaf()!!
+ val intendedIndent = if (child.elementType == KtStubElementTypes.VALUE_PARAMETER)
+ paramIndent else indent
+ if (prevLeaf.node.elementType == KtTokens.WHITE_SPACE) {
+ val spacing = prevLeaf.text
+ val cut = spacing.lastIndexOf("\n")
+ if (cut > -1) {
+ val childIndent = spacing.substring(cut)
+ if (childIndent == intendedIndent) {
+ continue@nextChild
+ }
+ emit(child.startOffset, "Unexpected indentation" +
+ " (expected ${intendedIndent.length - 1}, actual ${childIndent.length - 1})", true)
+ } else {
+ emit(child.startOffset, errorMessage(child), true)
+ }
+ if (autoCorrect) {
+ val prefix = if (cut > -1) spacing.substring(0, cut) else ""
+ prevLeaf.rawReplaceWithText(prefix + intendedIndent)
+ }
+ } else {
+ emit(child.startOffset, errorMessage(child), true)
+ if (autoCorrect) {
+ node.addChild(PsiWhiteSpaceImpl(intendedIndent), child)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun errorMessage(node: ASTNode) =
+ when (node.elementType) {
+ KtStubElementTypes.VALUE_PARAMETER ->
+ "Parameter should be on a separate line (unless all parameters can fit a single line)"
+ KtTokens.RPAR -> """Missing newline before ")""""
+ else -> throw UnsupportedOperationException()
+ }
+
+ private fun PsiElement.lineIndent(): String {
+ var leaf = PsiTreeUtil.prevLeaf(this)
+ while (leaf != null) {
+ if (leaf.node.elementType == KtTokens.WHITE_SPACE && leaf.textContains('\n')) {
+ return leaf.text.substring(leaf.text.lastIndexOf('\n') + 1)
+ }
+ leaf = PsiTreeUtil.prevLeaf(leaf)
+ }
+ return ""
+ }
+}
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 cdb5b0c..17a2ddd 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
@@ -10,7 +10,6 @@
FinalNewlineRule(),
// disabled until it's clear how to reconcile difference in Intellij & Android Studio import layout
// ImportOrderingRule(),
- NoLineBreakAfterElseRule(),
IndentationRule(),
MaxLineLengthRule(),
ModifierOrderRule(),
@@ -19,6 +18,7 @@
NoEmptyClassBodyRule(),
// disabled until it's clear what to do in case of `import _.it`
// NoItParamInMultilineLambdaRule(),
+ NoLineBreakAfterElseRule(),
NoLineBreakBeforeAssignmentRule(),
NoMultipleSpacesRule(),
NoSemicolonsRule(),
@@ -26,6 +26,7 @@
NoUnitReturnRule(),
NoUnusedImportsRule(),
NoWildcardImportsRule(),
+ ParameterListWrappingRule(),
SpacingAroundColonRule(),
SpacingAroundCommaRule(),
SpacingAroundCurlyRule(),
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt
index faec925..087a193 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt
@@ -1,12 +1,16 @@
package com.github.shyiko.ktlint.ruleset.standard
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.psi.KtStringTemplateEntry
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import kotlin.reflect.KClass
internal fun PsiElement.isPartOf(clazz: KClass<out PsiElement>) = getNonStrictParentOfType(clazz.java) != null
internal fun PsiElement.isPartOfString() = isPartOf(KtStringTemplateEntry::class)
+internal fun PsiElement.prevLeaf(): LeafPsiElement? = PsiTreeUtil.prevLeaf(this) as LeafPsiElement?
+internal fun PsiElement.nextLeaf(): LeafPsiElement? = PsiTreeUtil.nextLeaf(this) as LeafPsiElement?
internal fun <T> List<T>.head() = this.subList(0, this.size - 1)
internal fun <T> List<T>.tail() = this.subList(1, this.size)
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRuleTest.kt
index 0b0514e..866396e 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRuleTest.kt
@@ -8,7 +8,7 @@
class IndentationRuleTest {
@Test
- fun testRule() {
+ fun testLint() {
assertThat(IndentationRule().lint(
"""
/**
@@ -39,47 +39,37 @@
}
@Test
- fun testVerticallyAlignedParametersDoNotTriggerAnError() {
+ fun testLintNested() {
assertThat(IndentationRule().lint(
"""
- data class D(val a: Any,
- @Test val b: Any,
- val c: Any = 0) {
- }
-
- data class D2(
- val a: Any,
- val b: Any,
- val c: Any
- ) {
- }
-
- fun f(a: Any,
- b: Any,
- c: Any) {
- }
-
- fun f2(
- a: Any,
- b: Any,
- c: Any
- ) {
- }
- """.trimIndent()
+ fun funA() =
+ doStuff().use {
+ while (it.moveToNext()) {
+ doMore()
+ }
+ }
+ """.trimIndent(),
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
+ }
+
+ @Test
+ fun testLintCustomIndentSize() {
assertThat(IndentationRule().lint(
"""
- class A(
- //
- ) {}
- """.trimIndent()
+ fun main() {
+ val v = ""
+ println(v)
+ }
+ """.trimIndent(),
+ mapOf("indent_size" to "3")
)).isEqualTo(listOf(
- LintError(2, 1, "indent", "Unexpected indentation (3) (it should be 4)")
+ LintError(3, 1, "indent", "Unexpected indentation (4) (it should be 3)")
))
}
@Test
- fun testWithCustomIndentSize() {
+ fun testLintCustomIndentSizeValid() {
assertThat(IndentationRule().lint(
"""
/**
@@ -101,22 +91,7 @@
}
@Test
- fun testErrorWithCustomIndentSize() {
- assertThat(IndentationRule().lint(
- """
- fun main() {
- val v = ""
- println(v)
- }
- """.trimIndent(),
- mapOf("indent_size" to "3")
- )).isEqualTo(listOf(
- LintError(3, 1, "indent", "Unexpected indentation (4) (it should be 3)")
- ))
- }
-
- @Test
- fun testErrorWithIndentSizeUnset() {
+ fun testLintIndentSizeUnset() {
assertThat(IndentationRule().lint(
"""
fun main() {
@@ -129,23 +104,7 @@
}
@Test
- fun testShouldReportIncorrectIndentOfFirstParameter() {
- assertThat(IndentationRule().lint(
- """
- fun x(
- x: Int = 0,
- y: Int = 0
- ) {
- }
- """.trimIndent(),
- script = true
- )).isEqualTo(listOf(
- LintError(2, 1, "indent", "Unexpected indentation (5) (it should be 4)")
- ))
- }
-
- @Test
- fun testShouldRespectContinuationIndent() {
+ fun testLintContinuationIndent() {
assertThat(IndentationRule().lint(
"""
class TestContinuation {
@@ -160,13 +119,12 @@
}
}
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun `testUseContinuationIndentForConcatenation`() {
+ fun testLintContinuationIndentConcatenation() {
assertThat(IndentationRule().lint(
"""
class TestSubClass {
@@ -175,13 +133,12 @@
"")
}
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseContinuationIndentForDotQualifiedExpression() {
+ fun testLintContinuationIndentDotQualifiedExpression() {
assertThat(IndentationRule().lint(
"""
fun funA() {
@@ -189,13 +146,12 @@
.methodA()
}
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseIndentForObjectImplementation() {
+ fun testLintIndentInsideObjectBody() {
assertThat(IndentationRule().lint(
"""
@Test fun field() {
@@ -211,29 +167,13 @@
})
}
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
+ // fixme: should each argument be on a separate line?
@Test
- fun testComplexAssignmentQualifiedAccessAndFunctionBody() {
- assertThat(IndentationRule().lint(
- """
- fun funA() =
- doStuff().use {
- while (it.moveToNext()) {
- doMore()
- }
- }
- """.trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
- )).isEmpty()
- }
-
- @Test
- fun testUseContinuationIndentForArgumentList() {
+ fun testLintContinuationIndentFunctionCallArgumentList() {
assertThat(IndentationRule().lint(
"""
fun funA() {
@@ -241,14 +181,23 @@
listOf(ClassA(),
ClassB())
}
+ fun data() = listOf(
+ with(ClassA()) {
+ arrayOf({ paramA: TypeA ->
+ paramA.build()
+ }, funB())
+ },
+ arrayOf({ paramA: TypeA -> paramA.build() },
+ funB()
+ )
+ )
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseContinuationIndentInsideParenthesis() {
+ fun testLintContinuationIndentInsideParenthesis() {
assertThat(IndentationRule().lint(
"""
fun funA() {
@@ -262,50 +211,39 @@
)
}
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseIndentForCustomGetter() {
+ fun testLintIndentGettersAndSetters() {
assertThat(IndentationRule().lint(
"""
val storyBody: String
get() = String.format(body, "")
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseContinuationIndentAfterAssignment() {
+ fun testLintContinuationIndentSuperTypeList() {
assertThat(IndentationRule().lint(
"""
- val valueA =
- "{\"title\"}"
- """.trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
- )).isEmpty()
- }
-
- @Test
- fun testUseContinuationIndentForSuperTypeList() {
- assertThat(IndentationRule().lint(
- """
- class ClassA(fieldA: TypeA,
- fieldB: TypeB = DefaultB) :
+ class ClassA(fieldA: TypeA, fieldB: TypeB = DefaultB) :
SuperClassA(fieldA, fieldB)
- """.trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ class ClassA(a: TypeA) :
+ BasePresenter<View>() {
+
+ private lateinit var view: View
+ }
+ """.trimIndent(),
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseIndentForFunctionBody() {
+ fun testLintIndentInsideFunctionBody() {
assertThat(IndentationRule().lint(
"""
fun funA() {
@@ -314,53 +252,35 @@
assertThat(valueA.getFieldB()).isEqualTo(100L)
}
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseContinuationIndentInsideSuperTypeList() {
+ fun testLintContinuationIndentInsideSuperTypeList() {
assertThat(IndentationRule().lint(
"""
class ClassA : ClassB(), InterfaceA,
InterfaceB {
}
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseContinuationIndentForTypeProjection() {
+ fun testLintContinuationIndentTypeProjection() {
assertThat(IndentationRule().lint(
"""
val variable: SuperTpe<TypeA,
TypeB> = Implementation()
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
- )).isEmpty()
- }
-
- @Test(enabled = false)
- //not sure if it should use continuation indent or same as parameters
- fun testCommentBetweenParameterListShouldUseSameIndent() {
- assertThat(IndentationRule().lint(
- """
- data class MyClass(val a: String,
- val b: String,
- //comment between properties
- val c: String)
- """.trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseContinuationIndentForAssignment() {
+ fun testLintContinuationIndentAssignment() {
assertThat(IndentationRule().lint(
"""
fun funA() {
@@ -368,52 +288,54 @@
anotherFun()
}
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test()
- fun testUseContinuationIndentForTypeCasting() {
+ fun testLintContinuationIndentTypeCasting() {
assertThat(IndentationRule().lint(
"""
fun funA() = funB() as
TypeA
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseContinuationIndentForConstructorDelegation() {
+ fun testLintContinuationIndentConstructorDelegation() {
assertThat(IndentationRule().lint(
"""
class A : B() {
constructor(a: String) :
this(a)
}
- """.trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ class MyClass{
+ constructor(a: TypeA) :
+ super(a) {
+ init(a)
+ }
+ }
+ """.trimIndent(),
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun shouldUseContinuationInsideSafeQualifiedExpression() {
+ fun testLintContinuationIndentSafeChaining() {
assertThat(IndentationRule().lint(
"""
val valueA = call()
//comment
?.chainCallC { it.anotherCall() }
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testUseContinuationIndentForTypeDeclaration() {
+ fun testLintContinuationIndentTypeDeclaration() {
assertThat(IndentationRule().lint(
"""
private fun funA(a: Int, b: String):
@@ -421,91 +343,20 @@
return MyTypeA(a, b)
}
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
@Test
- fun testIgnoreSuperTypeListWhenCalculatePreviousIndent() {
- assertThat(IndentationRule().lint(
- """
- class ClassA(a: TypeA) :
- BasePresenter<View>() {
-
- private lateinit var view: View
- }
- """.trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
- )).isEmpty()
- }
-
- @Test
- fun testIgnoreConstructorDelegationCallWhenCalculatingPreviousIntent() {
- assertThat(IndentationRule().lint(
- """
- class MyClass{
- constructor(a: TypeA) :
- super(a) {
- init(a)
- }
- }
- """.trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
- )).isEmpty()
- }
-
- @Test(enabled = false)
- //Not sure it should be supported. Recommended way can be to put each argument on separate line
- fun testFuncIndent() {
- assertThat(IndentationRule().lint(
- """
- fun funA(a: A, b: B) {
- return funB(a,
- b, { (id) ->
- funC(id)
- }
- )
- }
- """.trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
- )).isEmpty()
- }
-
- @Test
- fun testComplexValueArgumentUsage() {
- assertThat(IndentationRule().lint(
- """
- fun data() = listOf(
- with(ClassA()) {
- arrayOf({ paramA: TypeA ->
- paramA.build()
- }, funB())
- },
- arrayOf({ paramA: TypeA -> paramA.build() },
- funB()
- )
- )
- """.trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
- )).isEmpty()
- }
-
- @Test
- fun testIgnoreCommentWhenCalculateParentIndent() {
+ fun testLintCommentsAreIgnored() {
assertThat(IndentationRule().lint(
"""
fun funA(argA: String) =
// comment
- // comment
+ // comment
call(argA)
""".trimIndent(),
- mapOf("indent_size" to "4",
- "continuation_indent_size" to "6")
+ mapOf("indent_size" to "4", "continuation_indent_size" to "6")
)).isEmpty()
}
}
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt
new file mode 100644
index 0000000..a8ef3a0
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt
@@ -0,0 +1,251 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.LintError
+import com.github.shyiko.ktlint.test.format
+import com.github.shyiko.ktlint.test.lint
+import org.assertj.core.api.Assertions.assertThat
+import org.testng.annotations.Test
+
+class ParameterListWrappingRuleTest {
+
+ @Test
+ fun testLintClassParameterList() {
+ assertThat(
+ ParameterListWrappingRule().lint(
+ """
+ class ClassA(paramA: String, paramB: String,
+ paramC: String)
+ """.trimIndent()
+ )
+ ).isEqualTo(
+ listOf(
+ LintError(1, 14, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+ LintError(1, 30, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+ LintError(2, 14, "parameter-list-wrapping", "Unexpected indentation (expected 4, actual 13)"),
+ LintError(2, 28, "parameter-list-wrapping", """Missing newline before ")"""")
+ )
+ )
+ }
+
+ @Test
+ fun testLintClassParameterListValid() {
+ assertThat(
+ ParameterListWrappingRule().lint(
+ """
+ class ClassA(paramA: String, paramB: String, paramC: String)
+ """.trimIndent()
+ )
+ ).isEmpty()
+ }
+
+ @Test
+ fun testLintClassParameterListValidMultiLine() {
+ assertThat(
+ ParameterListWrappingRule().lint(
+ """
+ class ClassA(
+ paramA: String,
+ paramB: String,
+ paramC: String
+ )
+ """.trimIndent()
+ )
+ ).isEmpty()
+ }
+
+ @Test
+ fun testFormatClassParameterList() {
+ assertThat(
+ ParameterListWrappingRule().format(
+ """
+ class ClassA(paramA: String, paramB: String,
+ paramC: String)
+ """.trimIndent()
+ )
+ ).isEqualTo(
+ """
+ class ClassA(
+ paramA: String,
+ paramB: String,
+ paramC: String
+ )
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testLintFunctionParameterList() {
+ assertThat(
+ ParameterListWrappingRule().lint(
+ """
+ fun f(a: Any,
+ b: Any,
+ c: Any) {
+ }
+ """.trimIndent()
+ )).isEqualTo(
+ listOf(
+ LintError(1, 7, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+ LintError(2, 7, "parameter-list-wrapping", "Unexpected indentation (expected 4, actual 6)"),
+ LintError(3, 7, "parameter-list-wrapping", "Unexpected indentation (expected 4, actual 6)"),
+ LintError(3, 13, "parameter-list-wrapping", """Missing newline before ")"""")
+ )
+ )
+ }
+
+ @Test
+ fun testFormatFunctionParameterList() {
+ assertThat(
+ ParameterListWrappingRule().format(
+ """
+ fun f(a: Any,
+ b: Any,
+ c: Any) {
+ }
+ """.trimIndent()
+ )).isEqualTo(
+ """
+ fun f(
+ a: Any,
+ b: Any,
+ c: Any
+ ) {
+ }
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testLambdaParametersAreIgnored() {
+ assertThat(
+ ParameterListWrappingRule().lint(
+ """
+ val fieldExample =
+ LongNameClass { paramA,
+ paramB,
+ paramC ->
+ ClassB(paramA, paramB, paramC)
+ }
+ """.trimIndent()
+ )).isEmpty()
+ }
+
+ @Test
+ fun testFormatPreservesIndent() {
+ assertThat(
+ ParameterListWrappingRule().format(
+ """
+ class A {
+ fun f(a: Any,
+ b: Any,
+ c: Any) {
+ }
+ }
+ """.trimIndent()
+ )).isEqualTo(
+ """
+ class A {
+ fun f(
+ a: Any,
+ b: Any,
+ c: Any
+ ) {
+ }
+ }
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testFormatCorrectsRPARIndentIfNeeded() {
+ assertThat(
+ ParameterListWrappingRule().format(
+ """
+ class A {
+ fun f(a: Any,
+ b: Any,
+ c: Any
+ ) {
+ }
+ }
+ """.trimIndent()
+ )).isEqualTo(
+ """
+ class A {
+ fun f(
+ a: Any,
+ b: Any,
+ c: Any
+ ) {
+ }
+ }
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testFormatNestedDeclarations() {
+ assertThat(
+ ParameterListWrappingRule().format(
+ """
+ fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String,
+ canBeAutoCorrected: Boolean) -> Unit
+ ) {}
+ """.trimIndent()
+ )).isEqualTo(
+ """
+ fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (
+ offset: Int,
+ errorMessage: String,
+ canBeAutoCorrected: Boolean
+ ) -> Unit
+ ) {}
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testFormatNestedDeclarationsValid() {
+ assertThat(
+ ParameterListWrappingRule().format(
+ """
+ fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ) {}
+ """.trimIndent()
+ )).isEqualTo(
+ """
+ fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ) {}
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testCommentsAreIgnored() {
+ assertThat(ParameterListWrappingRule().lint(
+ """
+ data class A(
+ /*
+ * comment
+ */
+ //
+ var v: String
+ )
+ """.trimIndent()
+ )).isEqualTo(listOf(
+ LintError(6, 4, "parameter-list-wrapping", "Unexpected indentation (expected 4, actual 3)")
+ ))
+ }
+}