blob: d270735d71adc06050e8b8446441b32cb3ae9b5b [file] [log] [blame]
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.PsiComment
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.psi.KtParameterList
import org.jetbrains.kotlin.psi.KtTypeConstraintList
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
class IndentationRule : Rule("indent") {
private var indentSize = -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 = gcd(maxOf(ec.indentSize, 1), maxOf(ec.continuationIndentSize, 1))
return
}
if (indentSize <= 1) {
return
}
if (node is PsiWhiteSpace) {
val lines = node.getText().split("\n")
if (lines.size > 1 && !node.isPartOf(PsiComment::class) && !node.isPartOf(KtTypeConstraintList::class)) {
var offset = node.startOffset + lines.first().length + 1
val previousIndentSize = node.previousIndentSize()
lines.tail().forEach { indent ->
if (indent.isNotEmpty() && (indent.length - previousIndentSize) % indentSize != 0) {
if (!node.isPartOf(KtParameterList::class)) { // parameter list wrapping enforced by ParameterListWrappingRule
emit(
offset,
"Unexpected indentation (${indent.length}) (it should be ${previousIndentSize + indentSize})",
false
)
}
}
offset += indent.length + 1
}
}
}
}
private fun gcd(a: Int, b: Int): Int = when {
a > b -> gcd(a - b, b)
a < b -> gcd(a, b - a)
else -> a
}
// todo: calculating indent based on the previous line value is wrong (see IndentationRule.testLint)
private fun ASTNode.previousIndentSize(): Int {
var node = this.treeParent?.psi
while (node != null) {
val nextNode = node.nextSibling?.node?.elementType
if (node is PsiWhiteSpace &&
nextNode != KtStubElementTypes.TYPE_REFERENCE &&
nextNode != KtStubElementTypes.SUPER_TYPE_LIST &&
nextNode != KtNodeTypes.CONSTRUCTOR_DELEGATION_CALL &&
node.textContains('\n') &&
node.nextLeaf()?.isPartOf(PsiComment::class) != true) {
return node.text.length - node.text.lastIndexOf('\n') - 1
}
node = node.prevSibling ?: node.parent
}
return 0
}
}