All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.shyiko.ktlint.ruleset.standard.IndentationRule.kt Maven / Gradle / Ivy

The newest version!
package com.github.shyiko.ktlint.ruleset.standard

import com.github.shyiko.ktlint.core.KtLint
import com.github.shyiko.ktlint.core.Rule
import com.github.shyiko.ktlint.core.ast.ElementType.CONSTRUCTOR_DELEGATION_CALL
import com.github.shyiko.ktlint.core.ast.ElementType.SUPER_TYPE_LIST
import com.github.shyiko.ktlint.core.ast.ElementType.TYPE_REFERENCE
import com.github.shyiko.ktlint.core.ast.isPartOf
import com.github.shyiko.ktlint.core.ast.isRoot
import com.github.shyiko.ktlint.core.ast.nextLeaf
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.psi.KtParameterList
import org.jetbrains.kotlin.psi.KtTypeConstraintList

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.isRoot()) {
            val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
            indentSize = editorConfig.indentSize
            return
        }
        if (indentSize <= 1) {
            return
        }
        if (node is PsiWhiteSpace && !node.isPartOf(PsiComment::class)) {
            val lines = node.getText().split("\n")
            if (lines.size > 1 && !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
                }
            }
            if (node.textContains('\t')) {
                val text = node.getText()
                emit(node.startOffset + text.indexOf('\t'), "Unexpected Tab character(s)", true)
                if (autoCorrect) {
                    (node as LeafPsiElement).rawReplaceWithText(text.replace("\t", " ".repeat(indentSize)))
                }
            }
        }
    }

    // todo: calculating indent based on the previous line value is wrong (see IndentationRule.testLint)
    private fun ASTNode.previousIndentSize(): Int {
        var node: ASTNode? = this.treeParent
        while (node != null) {
            val nextNode = node.treeNext?.elementType
            if (node is PsiWhiteSpace &&
                nextNode != TYPE_REFERENCE &&
                nextNode != SUPER_TYPE_LIST &&
                nextNode != CONSTRUCTOR_DELEGATION_CALL &&
                node.textContains('\n') &&
                node.nextLeaf()?.isPartOf(PsiComment::class) != true
            ) {
                val text = node.getText()
                return text.length - text.lastIndexOf('\n') - 1
            }
            node = node.treePrev ?: node.treeParent
        }
        return 0
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy