blob: 3c9c9e5dad5ef2a2a118dde3fe2b35895c23c9c1 [file] [log] [blame]
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.tree.TokenSet
import org.jetbrains.kotlin.lexer.KtTokens.ABSTRACT_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.ACTUAL_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.ANNOTATION_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.COMPANION_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.CONST_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.DATA_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.ENUM_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.EXPECT_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.EXTERNAL_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.FINAL_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.INFIX_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.INLINE_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.INNER_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.INTERNAL_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.LATEINIT_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.OPEN_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.OPERATOR_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.OVERRIDE_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.PRIVATE_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.PROTECTED_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.PUBLIC_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.SEALED_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.SUSPEND_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.TAILREC_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.VARARG_KEYWORD
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtDeclarationModifierList
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes.ANNOTATION_ENTRY
import java.util.Arrays
class ModifierOrderRule : Rule("modifier-order") {
// subset of KtTokens.MODIFIER_KEYWORDS_ARRAY (+ annotations entries)
private val order = arrayOf(
ANNOTATION_ENTRY,
PUBLIC_KEYWORD, PROTECTED_KEYWORD, PRIVATE_KEYWORD, INTERNAL_KEYWORD,
EXPECT_KEYWORD, ACTUAL_KEYWORD,
FINAL_KEYWORD, OPEN_KEYWORD, ABSTRACT_KEYWORD, SEALED_KEYWORD, CONST_KEYWORD,
EXTERNAL_KEYWORD,
OVERRIDE_KEYWORD,
LATEINIT_KEYWORD,
TAILREC_KEYWORD,
VARARG_KEYWORD,
SUSPEND_KEYWORD,
INNER_KEYWORD,
ENUM_KEYWORD, ANNOTATION_KEYWORD,
COMPANION_KEYWORD,
INLINE_KEYWORD,
INFIX_KEYWORD,
OPERATOR_KEYWORD,
DATA_KEYWORD
// NOINLINE_KEYWORD, CROSSINLINE_KEYWORD, OUT_KEYWORD, IN_KEYWORD, REIFIED_KEYWORD
// HEADER_KEYWORD, IMPL_KEYWORD
)
private val tokenSet = TokenSet.create(*order)
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.psi is KtDeclarationModifierList) {
val modifierArr = node.getChildren(tokenSet)
val sorted = modifierArr.copyOf().apply { sortWith(compareBy { order.indexOf(it.elementType) }) }
if (!Arrays.equals(modifierArr, sorted)) {
// Since annotations can be fairly lengthy and/or span multiple lines we are
// squashing them into a single placeholder text to guarantee a single line output
emit(node.startOffset, "Incorrect modifier order (should be \"${
squashAnnotations(sorted).joinToString(" ")
}\")", true)
if (autoCorrect) {
modifierArr.forEachIndexed { i, n ->
node.replaceChild(n, sorted[i].clone() as ASTNode)
}
}
}
}
}
private fun squashAnnotations(sorted: Array<ASTNode>): List<String> {
val nonAnnotationModifiers = sorted.filter { it.psi !is KtAnnotationEntry }
return if (nonAnnotationModifiers.size != sorted.size) {
listOf("@Annotation...") + nonAnnotationModifiers.map { it.text }
} else {
nonAnnotationModifiers.map { it.text }
}
}
}