aboutsummaryrefslogtreecommitdiff
path: root/ktlint-ruleset-standard/src/main/kotlin/com
diff options
context:
space:
mode:
Diffstat (limited to 'ktlint-ruleset-standard/src/main/kotlin/com')
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRule.kt127
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRule.kt35
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/EditorConfig.kt40
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt63
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRule.kt13
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRule.kt90
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRule.kt134
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRule.kt44
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt11
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRule.kt13
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRule.kt2
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRule.kt28
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRule.kt23
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt20
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoSemicolonsRule.kt7
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoTrailingSpacesRule.kt34
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnitReturnRule.kt8
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt36
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoWildcardImportsRule.kt7
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt151
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundColonRule.kt7
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRule.kt26
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRule.kt20
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRule.kt7
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRule.kt24
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRule.kt42
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt7
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StringTemplateRule.kt29
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt12
29 files changed, 887 insertions, 173 deletions
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 00000000..3c24344d
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRule.kt
@@ -0,0 +1,127 @@
+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.psi.PsiComment
+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.impl.source.tree.PsiWhiteSpaceImpl
+import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.lexer.KtTokens.ANDAND
+import org.jetbrains.kotlin.lexer.KtTokens.DIV
+import org.jetbrains.kotlin.lexer.KtTokens.DOT
+import org.jetbrains.kotlin.lexer.KtTokens.ELVIS
+import org.jetbrains.kotlin.lexer.KtTokens.MINUS
+import org.jetbrains.kotlin.lexer.KtTokens.MUL
+import org.jetbrains.kotlin.lexer.KtTokens.OROR
+import org.jetbrains.kotlin.lexer.KtTokens.PERC
+import org.jetbrains.kotlin.lexer.KtTokens.PLUS
+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") {
+
+ private val sameLineTokens = TokenSet.create(MUL, DIV, PERC, ANDAND, OROR)
+ private val prefixTokens = TokenSet.create(PLUS, MINUS)
+ 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)) {
+ if (node.psi.isPartOf(PsiComment::class)) {
+ return
+ }
+ val nextLeaf = node.psi.nextLeafIgnoringWhitespaceAndComments()?.prevLeaf(true)
+ if (nextLeaf is PsiWhiteSpaceImpl && nextLeaf.textContains('\n')) {
+ emit(node.startOffset, "Line must not end with \"${node.text}\"", true)
+ if (autoCorrect) {
+ // rewriting
+ // <prevLeaf><node="."><nextLeaf="\n"> to
+ // <prevLeaf><delete space if any><nextLeaf="\n"><node="."><space if needed>
+ // (or)
+ // <prevLeaf><node="."><spaceBeforeComment><comment><nextLeaf="\n"> to
+ // <prevLeaf><delete space if any><spaceBeforeComment><comment><nextLeaf="\n"><node="."><space if needed>
+ val prevLeaf = node.psi.prevLeaf(true)
+ if (prevLeaf is PsiWhiteSpaceImpl) {
+ prevLeaf.node.treeParent.removeChild(prevLeaf.node)
+ }
+ if (!noSpaceAroundTokens.contains(elementType)) {
+ nextLeaf.rawInsertAfterMe(PsiWhiteSpaceImpl(" "))
+ }
+ node.treeParent.removeChild(node)
+ nextLeaf.rawInsertAfterMe(node.psi as LeafPsiElement)
+ }
+ }
+ } else if (sameLineTokens.contains(elementType) || prefixTokens.contains(elementType)) {
+ if (node.psi.isPartOf(PsiComment::class)) {
+ return
+ }
+ val prevLeaf = node.psi.prevLeaf(true)
+ if (
+ prevLeaf is PsiWhiteSpaceImpl &&
+ prevLeaf.textContains('\n') &&
+ // fn(*typedArray<...>()) case
+ (elementType != MUL || !prevLeaf.isPartOfSpread()) &&
+ // unary +/-
+ (!prefixTokens.contains(elementType) || !node.isInPrefixPosition()) &&
+ // LeafPsiElement->KtOperationReferenceExpression->KtPrefixExpression->KtWhenConditionWithExpression
+ !node.isPartOfWhenCondition()
+ ) {
+ emit(node.startOffset, "Line must not begin with \"${node.text}\"", true)
+ if (autoCorrect) {
+ // rewriting
+ // <insertionPoint><prevLeaf="\n"><node="&&"><nextLeaf=" "> to
+ // <insertionPoint><prevLeaf=" "><node="&&"><nextLeaf="\n"><delete node="&&"><delete nextLeaf=" ">
+ // (or)
+ // <insertionPoint><spaceBeforeComment><comment><prevLeaf="\n"><node="&&"><nextLeaf=" "> to
+ // <insertionPoint><space if needed><node="&&"><spaceBeforeComment><comment><prevLeaf="\n"><delete node="&&"><delete nextLeaf=" ">
+ val nextLeaf = node.psi.nextLeaf(true)
+ if (nextLeaf is PsiWhiteSpaceImpl) {
+ nextLeaf.node.treeParent.removeChild(nextLeaf.node)
+ }
+ val insertionPoint = prevLeaf.prevLeafIgnoringWhitespaceAndComments() as LeafPsiElement
+ node.treeParent.removeChild(node)
+ insertionPoint.rawInsertAfterMe(node.psi as LeafPsiElement)
+ if (!noSpaceAroundTokens.contains(elementType)) {
+ insertionPoint.rawInsertAfterMe(PsiWhiteSpaceImpl(" "))
+ }
+ }
+ }
+ }
+ }
+
+ private fun PsiElement.isPartOfSpread() =
+ prevLeafIgnoringWhitespaceAndComments()?.let { leaf ->
+ val type = leaf.node.elementType
+ type == KtTokens.LPAR ||
+ type == KtTokens.COMMA ||
+ type == KtTokens.LBRACE ||
+ type == KtTokens.ELSE_KEYWORD ||
+ KtTokens.OPERATIONS.contains(type)
+ } == true
+
+ private fun ASTNode.isInPrefixPosition() =
+ treeParent?.treeParent?.elementType == KtNodeTypes.PREFIX_EXPRESSION
+
+ private fun ASTNode.isPartOfWhenCondition() =
+ treeParent?.treeParent?.treeParent?.elementType == KtNodeTypes.WHEN_CONDITION_EXPRESSION
+
+ private fun PsiElement.nextLeafIgnoringWhitespaceAndComments() =
+ this.nextLeaf { it.node.elementType != KtTokens.WHITE_SPACE && !it.isPartOf(PsiComment::class) }
+
+ private fun PsiElement.prevLeafIgnoringWhitespaceAndComments() =
+ this.prevLeaf { it.node.elementType != KtTokens.WHITE_SPACE && !it.isPartOf(PsiComment::class) }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRule.kt
new file mode 100644
index 00000000..68ba62b0
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRule.kt
@@ -0,0 +1,35 @@
+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.PsiComment
+import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
+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.util.PsiTreeUtil
+
+class CommentSpacingRule : Rule("comment-spacing") {
+
+ override fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ) {
+ if (node is PsiComment && node is LeafPsiElement && node.getText().startsWith("//")) {
+ val prevLeaf = PsiTreeUtil.prevLeaf(node)
+ if (prevLeaf !is PsiWhiteSpace && prevLeaf is LeafPsiElement) {
+ emit(node.startOffset, "Missing space before //", true)
+ if (autoCorrect) {
+ node.rawInsertBeforeMe(PsiWhiteSpaceImpl(" "))
+ }
+ }
+ val text = node.getText()
+ if (text.length != 2 && !text.startsWith("// ")) {
+ emit(node.startOffset, "Missing space after //", true)
+ if (autoCorrect) {
+ node.rawReplaceWithText("// " + text.removePrefix("//"))
+ }
+ }
+ }
+ }
+}
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 00000000..64a56484
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/EditorConfig.kt
@@ -0,0 +1,40 @@
+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
+
+ // 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() ?: indentSize
+ }
+ 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/FilenameRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt
new file mode 100644
index 00000000..8c7169c8
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt
@@ -0,0 +1,63 @@
+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.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
+import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
+import java.nio.file.Paths
+
+/**
+ * If there is only one top level class/object/typealias in a given file, then its name should match the file's name.
+ */
+class FilenameRule : Rule("filename"), Rule.Modifier.RestrictToRoot {
+
+ private val ignoreSet = setOf<IElementType>(
+ KtStubElementTypes.FILE_ANNOTATION_LIST,
+ KtStubElementTypes.PACKAGE_DIRECTIVE,
+ KtStubElementTypes.IMPORT_LIST,
+ KtTokens.WHITE_SPACE,
+ KtTokens.EOL_COMMENT,
+ KtTokens.BLOCK_COMMENT,
+ KtTokens.DOC_COMMENT,
+ KtTokens.SHEBANG_COMMENT
+ )
+
+ override fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ) {
+ val filePath = node.getUserData(KtLint.FILE_PATH_USER_DATA_KEY)
+ if (filePath?.endsWith(".kt") != true) {
+ // ignore all non ".kt" files (including ".kts")
+ return
+ }
+ var type: String? = null
+ var className: String? = null
+ for (el in node.getChildren(null)) {
+ if (el.elementType == KtStubElementTypes.CLASS ||
+ el.elementType == KtStubElementTypes.OBJECT_DECLARATION ||
+ el.elementType == KtStubElementTypes.TYPEALIAS) {
+ if (className != null) {
+ // more than one class/object/typealias present
+ return
+ }
+ val id = el.findChildByType(KtTokens.IDENTIFIER)
+ type = id?.psi?.getPrevSiblingIgnoringWhitespaceAndComments(false)?.text
+ className = id?.text
+ } else if (!ignoreSet.contains(el.elementType)) {
+ // https://github.com/android/android-ktx/blob/master/src/main/java/androidx/core/graphics/Path.kt case
+ return
+ }
+ }
+ if (className != null) {
+ val name = Paths.get(filePath).fileName.toString().substringBefore(".")
+ if (name != "package" && name != className) {
+ emit(0, "$type $className should be declared in a file named $className.kt", false)
+ }
+ }
+ }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRule.kt
index 67057e6e..93e80290 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRule.kt
@@ -1,13 +1,13 @@
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.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
-class FinalNewlineRule : Rule("final-newline") {
+class FinalNewlineRule : Rule("final-newline"), Rule.Modifier.RestrictToRoot {
override fun visit(
node: ASTNode,
@@ -15,9 +15,9 @@ class FinalNewlineRule : Rule("final-newline") {
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.elementType == KtStubElementTypes.FILE) {
- val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
- val insertFinalNewline = editorConfig.get("insert_final_newline")?.toBoolean() ?: return
- val lastNode = node.lastChildNode
+ val ec = EditorConfig.from(node as FileASTNode)
+ val insertFinalNewline = ec.insertFinalNewline ?: return
+ val lastNode = lastChildNodeOf(node)
if (insertFinalNewline) {
if (lastNode !is PsiWhiteSpace || !lastNode.textContains('\n')) {
// (PsiTreeUtil.getDeepestLast(lastNode.psi).node ?: lastNode).startOffset
@@ -36,4 +36,7 @@ class FinalNewlineRule : Rule("final-newline") {
}
}
}
+
+ private tailrec fun lastChildNodeOf(node: ASTNode): ASTNode? =
+ if (node.lastChildNode == null) node else lastChildNodeOf(node.lastChildNode)
}
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 87b833af..d270735d 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,68 +1,74 @@
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.openapi.util.TextRange
+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.com.intellij.psi.util.PsiTreeUtil
-import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
-import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtParameterList
-import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
-import org.jetbrains.kotlin.psi.psiUtil.startOffset
+import org.jetbrains.kotlin.psi.KtTypeConstraintList
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 var indent = DEFAULT_INDENT
+ private var indentSize = -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")
- indent = indentSize?.toIntOrNull() ?: if (indentSize?.toLowerCase() == "unset") -1 else indent
+ val ec = EditorConfig.from(node as FileASTNode)
+ indentSize = gcd(maxOf(ec.indentSize, 1), maxOf(ec.continuationIndentSize, 1))
return
}
- if (indent <= 0) {
+ if (indentSize <= 1) {
return
}
- if (node is PsiWhiteSpace && !node.isPartOf(PsiComment::class)) {
+ if (node is PsiWhiteSpace) {
val lines = node.getText().split("\n")
- if (lines.size > 1) {
+ if (lines.size > 1 && !node.isPartOf(PsiComment::class) && !node.isPartOf(KtTypeConstraintList::class)) {
var offset = node.startOffset + lines.first().length + 1
- val firstParameterColumn = lazy {
- val firstParameter = PsiTreeUtil.findChildOfType(
- node.getNonStrictParentOfType(KtParameterList::class.java),
- KtParameter::class.java
- )
- firstParameter?.run {
- DiagnosticUtils.getLineAndColumnInPsiFile(node.containingFile,
- TextRange(startOffset, startOffset)).column
- } ?: 0
- }
- lines.tail().forEach { line ->
- if (line.length % indent != 0) {
- if (node.isPartOf(KtParameterList::class) && firstParameterColumn.value != 0) {
- 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 {
- emit(offset, "Unexpected indentation (${line.length}) (it should be multiple of $indent)", false)
+ 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 += line.length + 1
+ 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
+ }
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRule.kt
index 58d784e0..aa7cca16 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRule.kt
@@ -1,16 +1,21 @@
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.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.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
-class MaxLineLengthRule : Rule("max-line-length") {
+class MaxLineLengthRule : Rule("max-line-length"), Rule.Modifier.Last {
+
+ private var maxLineLength: Int = -1
+ private var rangeTree = RangeTree()
override fun visit(
node: ASTNode,
@@ -18,31 +23,134 @@ class MaxLineLengthRule : Rule("max-line-length") {
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.elementType == KtStubElementTypes.FILE) {
- val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
- val maxLineLength = editorConfig.get("max_line_length")?.toIntOrNull() ?: 0
+ val ec = EditorConfig.from(node as FileASTNode)
+ maxLineLength = ec.maxLineLength
if (maxLineLength <= 0) {
return
}
+ val errorOffset = arrayListOf<Int>()
val text = node.text
val lines = text.split("\n")
var offset = 0
for (line in lines) {
if (line.length > maxLineLength) {
val el = node.psi.findElementAt(offset + line.length - 1)!!
- if (!el.isPartOf(PsiComment::class)) {
- if (!el.isPartOf(KtPackageDirective::class) && !el.isPartOf(KtImportDirective::class)) {
- emit(offset, "Exceeded max line length ($maxLineLength)", false)
- }
- } else {
- // if comment is the only thing on the line - fine, otherwise emit an error
- val prevLeaf = el.getPrevSiblingIgnoringWhitespaceAndComments(false)
- if (prevLeaf != null && prevLeaf.startOffset >= offset) {
- emit(offset, "Exceeded max line length ($maxLineLength)", false)
+ if (!el.isPartOf(KDoc::class)) {
+ if (!el.isPartOf(PsiComment::class)) {
+ if (!el.isPartOf(KtPackageDirective::class) && !el.isPartOf(KtImportDirective::class)) {
+ // fixme:
+ // normally we would emit here but due to API limitations we need to hold off until
+ // node spanning the same offset is 'visit'ed
+ // (for ktlint-disable directive to have effect (when applied))
+ // this will be rectified in the upcoming release(s)
+ errorOffset.add(offset)
+ }
+ } else {
+ // if comment is the only thing on the line - fine, otherwise emit an error
+ val prevLeaf = el.getPrevSiblingIgnoringWhitespaceAndComments(false)
+ if (prevLeaf != null && prevLeaf.startOffset >= offset) {
+ // fixme:
+ // normally we would emit here but due to API limitations we need to hold off until
+ // node spanning the same offset is 'visit'ed
+ // (for ktlint-disable directive to have effect (when applied))
+ // this will be rectified in the upcoming release(s)
+ errorOffset.add(offset)
+ }
}
}
}
offset += line.length + 1
}
+ rangeTree = RangeTree(errorOffset)
+ } else if (!rangeTree.isEmpty() && node.psi is LeafPsiElement) {
+ rangeTree
+ .query(node.startOffset, node.startOffset + node.textLength)
+ .forEach { offset ->
+ emit(offset, "Exceeded max line length ($maxLineLength)", false)
+ }
+ }
+ }
+}
+
+class RangeTree(seq: List<Int> = emptyList()) {
+
+ private var emptyArrayView = ArrayView(0, 0)
+ private var arr: IntArray = seq.toIntArray()
+
+ init {
+ if (arr.isNotEmpty()) {
+ arr.reduce { p, n -> require(p <= n) { "Input must be sorted" }; n }
+ }
+ }
+
+ // runtime: O(log(n)+k), where k is number of matching points
+ // space: O(1)
+ fun query(vmin: Int, vmax: Int): ArrayView {
+ var r = arr.size - 1
+ if (r == -1 || vmax < arr[0] || arr[r] < vmin) {
+ return emptyArrayView
+ }
+ // binary search for min(arr[l] >= vmin)
+ var l = 0
+ while (l < r) {
+ val m = (r + l) / 2
+ if (vmax < arr[m]) {
+ r = m - 1
+ } else if (arr[m] < vmin) {
+ l = m + 1
+ } else {
+ // arr[l] ?<=? vmin <= arr[m] <= vmax ?<=? arr[r]
+ if (vmin <= arr[l]) break else l++ // optimization
+ r = m
+ }
+ }
+ if (l > r || arr[l] < vmin) {
+ return emptyArrayView
+ }
+ // find max(k) such as arr[k] < vmax
+ var k = l
+ while (k < arr.size) {
+ if (arr[k] >= vmax) {
+ break
+ }
+ k++
+ }
+ return ArrayView(l, k)
+ }
+
+ fun isEmpty() = arr.isEmpty()
+
+ inner class ArrayView(private var l: Int, private val r: Int) {
+
+ val size: Int = r - l
+
+ fun get(i: Int): Int {
+ if (i < 0 || i >= size) {
+ throw IndexOutOfBoundsException()
+ }
+ return arr[l + i]
+ }
+
+ inline fun forEach(cb: (v: Int) -> Unit) {
+ var i = 0
+ while (i < size) {
+ cb(get(i++))
+ }
+ }
+
+ override fun toString(): String {
+ if (l == r) {
+ return "[]"
+ }
+ val sb = StringBuilder("[")
+ var i = l
+ while (i < r) {
+ sb.append(arr[i]).append(", ")
+ i++
+ }
+ sb.replace(sb.length - 2, sb.length, "")
+ sb.append("]")
+ return sb.toString()
}
}
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRule.kt
index 4815c065..3c9c9e5d 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRule.kt
@@ -2,14 +2,15 @@ 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.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
@@ -26,25 +27,34 @@ 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
+ // subset of KtTokens.MODIFIER_KEYWORDS_ARRAY (+ annotations entries)
private val order = arrayOf(
+ ANNOTATION_ENTRY,
PUBLIC_KEYWORD, PROTECTED_KEYWORD, PRIVATE_KEYWORD, INTERNAL_KEYWORD,
- FINAL_KEYWORD, OPEN_KEYWORD, ABSTRACT_KEYWORD,
- SUSPEND_KEYWORD, TAILREC_KEYWORD,
+ EXPECT_KEYWORD, ACTUAL_KEYWORD,
+ FINAL_KEYWORD, OPEN_KEYWORD, ABSTRACT_KEYWORD, SEALED_KEYWORD, CONST_KEYWORD,
+ EXTERNAL_KEYWORD,
OVERRIDE_KEYWORD,
- CONST_KEYWORD, LATEINIT_KEYWORD,
- INNER_KEYWORD, EXTERNAL_KEYWORD,
- ENUM_KEYWORD, ANNOTATION_KEYWORD, SEALED_KEYWORD, DATA_KEYWORD,
+ LATEINIT_KEYWORD,
+ TAILREC_KEYWORD,
+ VARARG_KEYWORD,
+ SUSPEND_KEYWORD,
+ INNER_KEYWORD,
+ ENUM_KEYWORD, ANNOTATION_KEYWORD,
COMPANION_KEYWORD,
INLINE_KEYWORD,
- // NOINLINE_KEYWORD, CROSSINLINE_KEYWORD, OUT_KEYWORD, IN_KEYWORD, VARARG_KEYWORD, REIFIED_KEYWORD
INFIX_KEYWORD,
- OPERATOR_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)
@@ -58,16 +68,26 @@ class ModifierOrderRule : Rule("modifier-order") {
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 \"${
- sorted.map { it.text }.joinToString(" ")
+ squashAnnotations(sorted).joinToString(" ")
}\")", true)
if (autoCorrect) {
modifierArr.forEachIndexed { i, n ->
- // fixme: find a better way (node type is now potentially out of sync)
- (n.psi as LeafPsiElement).replaceWithText(sorted[i].text)
+ 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 }
+ }
+ }
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt
index ce64489f..cd7649e1 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt
@@ -9,17 +9,20 @@ import org.jetbrains.kotlin.lexer.KtTokens
class NoBlankLineBeforeRbraceRule : Rule("no-blank-line-before-rbrace") {
- 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 is PsiWhiteSpace &&
node.textContains('\n') &&
PsiTreeUtil.nextLeaf(node, true)?.node?.elementType == KtTokens.RBRACE) {
val split = node.getText().split("\n")
if (split.size > 2) {
emit(node.startOffset + split[0].length + split[1].length + 1,
- "Needless blank line(s)", true)
+ "Unexpected blank line(s) before \"}\"", true)
if (autoCorrect) {
- (node as LeafPsiElement).replaceWithText("${split.first()}\n${split.last()}")
+ (node as LeafPsiElement).rawReplaceWithText("${split.first()}\n${split.last()}")
}
}
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRule.kt
index 1e6142f8..ae7c582d 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRule.kt
@@ -4,17 +4,22 @@ import com.github.shyiko.ktlint.core.Rule
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
class NoConsecutiveBlankLinesRule : Rule("no-consecutive-blank-lines") {
- 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 is PsiWhiteSpace) {
val split = node.getText().split("\n")
- if (split.size > 3) {
+ if (split.size > 3 || split.size == 3 && PsiTreeUtil.nextLeaf(node) == null /* eof */) {
emit(node.startOffset + split[0].length + split[1].length + 2, "Needless blank line(s)", true)
if (autoCorrect) {
- (node as LeafPsiElement).replaceWithText("${split.first()}\n\n${split.last()}")
+ (node as LeafPsiElement)
+ .rawReplaceWithText("${split.first()}\n${if (split.size > 3) "\n" else ""}${split.last()}")
}
}
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRule.kt
index 59a57a34..5e412204 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRule.kt
@@ -25,7 +25,7 @@ class NoEmptyClassBodyRule : Rule("no-empty-class-body") {
if (autoCorrect) {
val prevNode = node.psi.prevSibling.node
val nextNode = PsiTreeUtil.nextLeaf(node.psi, true)?.node
- if (prevNode.elementType == KtTokens.WHITE_SPACE && nextNode?.elementType == KtTokens.WHITE_SPACE) {
+ if (prevNode.elementType == KtTokens.WHITE_SPACE && (nextNode == null || nextNode.elementType == KtTokens.WHITE_SPACE)) {
// remove space between declaration and block
prevNode.treeParent.removeChild(prevNode)
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRule.kt
new file mode 100644
index 00000000..d28ba5eb
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRule.kt
@@ -0,0 +1,28 @@
+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.PsiWhiteSpace
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.lexer.KtTokens
+
+class NoLineBreakAfterElseRule : Rule("no-line-break-after-else") {
+
+ override fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ) {
+ if (node is PsiWhiteSpace &&
+ node.textContains('\n')) {
+ if (PsiTreeUtil.prevLeaf(node, true)?.node?.elementType == KtTokens.ELSE_KEYWORD &&
+ PsiTreeUtil.nextLeaf(node, true)?.node?.elementType.let { it == KtTokens.IF_KEYWORD || it == KtTokens.LBRACE }) {
+ emit(node.startOffset + 1, "Unexpected line break after \"else\"", true)
+ if (autoCorrect) {
+ (node as LeafPsiElement).rawReplaceWithText(" ")
+ }
+ }
+ }
+ }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRule.kt
new file mode 100644
index 00000000..effbd187
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRule.kt
@@ -0,0 +1,23 @@
+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.PsiWhiteSpace
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.lexer.KtTokens
+
+class NoLineBreakBeforeAssignmentRule : Rule("no-line-break-before-assignment") {
+
+ override fun visit(node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+ if (node.elementType == KtTokens.EQ) {
+ val prevElement = node.treePrev?.psi
+ if (prevElement is PsiWhiteSpace && prevElement.text.contains("\n")) {
+ emit(node.startOffset, "Line break before assignment is not allowed", true)
+ if (autoCorrect) {
+ (node.treeNext?.psi as LeafPsiElement).rawReplaceWithText(prevElement.text)
+ (prevElement as LeafPsiElement).rawReplaceWithText(" ")
+ }
+ }
+ }
+ }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt
index 36cf26fe..15aa1a2a 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt
@@ -33,7 +33,8 @@ class NoMultipleSpacesRule : Rule("no-multi-spaces") {
val psi = node.psi
if (psi is PsiComment) { comments.add(psi) }
}
- return comments.foldIndexed(mutableMapOf<Offset, CommentRelativeLocation>()) { i, acc, comment ->
+ return comments.foldIndexed(mutableMapOf()) { i, acc, comment ->
+ // todo: get rid of DiagnosticUtils (IndexOutOfBoundsException)
val pos = DiagnosticUtils.getLineAndColumnInPsiFile(fileNode.psi as PsiFile,
TextRange(comment.startOffset, comment.startOffset))
acc.put(comment.startOffset, CommentRelativeLocation(
@@ -46,12 +47,14 @@ class NoMultipleSpacesRule : Rule("no-multi-spaces") {
}
}
- 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) {
fileNode = node
- } else
- if (node is PsiWhiteSpace && !node.textContains('\n') && node.getTextLength() > 1) {
+ } else if (node is PsiWhiteSpace && !node.textContains('\n') && node.getTextLength() > 1) {
val nextLeaf = PsiTreeUtil.nextLeaf(node, true)
if (nextLeaf is PsiComment) {
val positionMap = commentMap
@@ -71,13 +74,8 @@ class NoMultipleSpacesRule : Rule("no-multi-spaces") {
}
emit(node.startOffset + 1, "Unnecessary space(s)", true)
if (autoCorrect) {
- (node as LeafPsiElement).replaceWithText(" ")
+ (node as LeafPsiElement).rawReplaceWithText(" ")
}
}
}
-
- private fun ASTNode.visit(cb: (node: ASTNode) -> Unit) {
- cb(this)
- this.getChildren(null).forEach { it.visit(cb) }
- }
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoSemicolonsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoSemicolonsRule.kt
index fce5ce3c..770e4090 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoSemicolonsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoSemicolonsRule.kt
@@ -10,8 +10,11 @@ import org.jetbrains.kotlin.psi.KtEnumEntry
class NoSemicolonsRule : Rule("no-semi") {
- 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 is LeafPsiElement && node.textMatches(";") && !node.isPartOfString() &&
!node.isPartOf(KtEnumEntry::class)) {
val nextLeaf = PsiTreeUtil.nextLeaf(node, true)
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoTrailingSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoTrailingSpacesRule.kt
index fa3e492f..a7e03c88 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoTrailingSpacesRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoTrailingSpacesRule.kt
@@ -8,33 +8,41 @@ import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
class NoTrailingSpacesRule : Rule("no-trailing-spaces") {
- 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 is PsiWhiteSpace) {
val lines = node.getText().split("\n")
if (lines.size > 1) {
- checkForTrailingSpaces(lines.head(), node.startOffset, emit)
- if (autoCorrect) {
- (node as LeafPsiElement).replaceWithText("\n".repeat(lines.size - 1) + lines.last())
+ val violated = checkForTrailingSpaces(lines.head(), node.startOffset, emit)
+ if (violated && autoCorrect) {
+ (node as LeafPsiElement).rawReplaceWithText("\n".repeat(lines.size - 1) + lines.last())
}
- } else
- if (PsiTreeUtil.nextLeaf(node) == null /* eof */) {
- checkForTrailingSpaces(lines, node.startOffset, emit)
- if (autoCorrect) {
- (node as LeafPsiElement).replaceWithText("\n".repeat(lines.size - 1))
+ } else if (PsiTreeUtil.nextLeaf(node) == null /* eof */) {
+ val violated = checkForTrailingSpaces(lines, node.startOffset, emit)
+ if (violated && autoCorrect) {
+ (node as LeafPsiElement).rawReplaceWithText("\n".repeat(lines.size - 1))
}
}
}
}
- private fun checkForTrailingSpaces(lines: List<String>, offset: Int,
- emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+ private fun checkForTrailingSpaces(
+ lines: List<String>,
+ offset: Int,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ): Boolean {
+ var violated = false
var violationOffset = offset
- return lines.forEach { line ->
+ lines.forEach { line ->
if (!line.isEmpty()) {
emit(violationOffset, "Trailing space(s)", true)
+ violated = true
}
violationOffset += line.length + 1
}
+ return violated
}
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnitReturnRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnitReturnRule.kt
index 205448a3..c911cbbd 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnitReturnRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnitReturnRule.kt
@@ -13,10 +13,10 @@ class NoUnitReturnRule : Rule("no-unit-return") {
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
- if (node.elementType == KtStubElementTypes.TYPE_REFERENCE
- && node.treeParent.elementType == KtStubElementTypes.FUNCTION
- && node.text.contentEquals("Unit")
- && PsiTreeUtil.nextVisibleLeaf(node.psi)?.node?.elementType == KtTokens.LBRACE) {
+ if (node.elementType == KtStubElementTypes.TYPE_REFERENCE &&
+ node.treeParent.elementType == KtStubElementTypes.FUNCTION &&
+ node.text.contentEquals("Unit") &&
+ PsiTreeUtil.nextVisibleLeaf(node.psi)?.node?.elementType == KtTokens.LBRACE) {
emit(node.startOffset, "Unnecessary \"Unit\" return type", true)
if (autoCorrect) {
var prevNode = node
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt
index cbb8287d..b46f04df 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt
@@ -12,6 +12,8 @@ import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
class NoUnusedImportsRule : Rule("no-unused-imports") {
+ private val componentNRegex = Regex("^component\\d+$")
+
private val operatorSet = setOf(
// unary
"unaryPlus", "unaryMinus", "not",
@@ -34,46 +36,45 @@ class NoUnusedImportsRule : Rule("no-unused-imports") {
// iteration (https://github.com/shyiko/ktlint/issues/40)
"iterator",
// by (https://github.com/shyiko/ktlint/issues/54)
- "getValue", "setValue",
- // destructuring assignment
- "component1", "component2", "component3", "component4", "component5"
+ "getValue", "setValue"
)
- private val ref = mutableSetOf("*")
+ private val ref = mutableSetOf<String>()
private var packageName = ""
- 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) {
+ ref.clear() // rule can potentially be executed more than once (when formatting)
+ ref.add("*")
node.visit { vnode ->
val psi = vnode.psi
val type = vnode.elementType
if (type == KDocTokens.MARKDOWN_LINK && psi is KDocLink) {
val linkText = psi.getLinkText().replace("`", "")
ref.add(linkText.split('.').first())
- } else
- if ((type == KtNodeTypes.REFERENCE_EXPRESSION || type == KtNodeTypes.OPERATION_REFERENCE) &&
+ } else if ((type == KtNodeTypes.REFERENCE_EXPRESSION || type == KtNodeTypes.OPERATION_REFERENCE) &&
!psi.isPartOf(KtImportDirective::class)) {
ref.add(vnode.text.trim('`'))
}
}
- } else
- if (node.elementType == KtStubElementTypes.PACKAGE_DIRECTIVE) {
+ } else if (node.elementType == KtStubElementTypes.PACKAGE_DIRECTIVE) {
val packageDirective = node.psi as KtPackageDirective
packageName = packageDirective.qualifiedName
- } else
- if (node.elementType == KtStubElementTypes.IMPORT_DIRECTIVE) {
+ } else if (node.elementType == KtStubElementTypes.IMPORT_DIRECTIVE) {
val importDirective = node.psi as KtImportDirective
val name = importDirective.importPath?.importedName?.asString()
val importPath = importDirective.importPath?.pathStr!!
if (importDirective.aliasName == null &&
- importPath.startsWith(packageName) &&
+ (packageName.isEmpty() || importPath.startsWith("$packageName.")) &&
importPath.substring(packageName.length + 1).indexOf('.') == -1) {
emit(importDirective.startOffset, "Unnecessary import", true)
if (autoCorrect) {
importDirective.delete()
}
- } else
- if (name != null && !ref.contains(name) && !operatorSet.contains(name)) {
+ } else if (name != null && !ref.contains(name) && !operatorSet.contains(name) && !name.isComponentN()) {
emit(importDirective.startOffset, "Unused import", true)
if (autoCorrect) {
importDirective.delete()
@@ -82,8 +83,5 @@ class NoUnusedImportsRule : Rule("no-unused-imports") {
}
}
- private fun ASTNode.visit(cb: (node: ASTNode) -> Unit) {
- cb(this)
- this.getChildren(null).forEach { it.visit(cb) }
- }
+ private fun String.isComponentN() = componentNRegex.matches(this)
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoWildcardImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoWildcardImportsRule.kt
index db4e7bb9..9cb18aa1 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoWildcardImportsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoWildcardImportsRule.kt
@@ -7,8 +7,11 @@ import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
class NoWildcardImportsRule : Rule("no-wildcard-imports") {
- 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.IMPORT_DIRECTIVE) {
val importDirective = node.psi as KtImportDirective
val path = importDirective.importPath?.pathStr
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 00000000..81d06038
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt
@@ -0,0 +1,151 @@
+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.PsiWhiteSpace
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement
+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.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 = -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 (indentSize <= 0) {
+ 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') ||
+ // max_line_length exceeded
+ maxLineLength > -1 && (node.psi.column - 1 + node.textLength) > maxLineLength
+ if (putParametersOnSeparateLines) {
+ // aiming for
+ // ... LPAR
+ // <line indent + indentSize> VALUE_PARAMETER...
+ // <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) {
+ KtTokens.LPAR -> {
+ val prevLeaf = child.psi.prevLeaf()
+ if (prevLeaf is PsiWhiteSpace && prevLeaf.textContains('\n')) {
+ emit(child.startOffset, errorMessage(child), true)
+ if (autoCorrect) {
+ prevLeaf.delete()
+ }
+ }
+ }
+ KtStubElementTypes.VALUE_PARAMETER,
+ KtTokens.RPAR -> {
+ var paramInnerIndentAdjustment = 0
+ val prevLeaf = child.psi.prevLeaf()
+ val intendedIndent = if (child.elementType == KtStubElementTypes.VALUE_PARAMETER)
+ paramIndent else indent
+ if (prevLeaf is PsiWhiteSpace) {
+ 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 adjustedIndent = (if (cut > -1) spacing.substring(0, cut) else "") + intendedIndent
+ paramInnerIndentAdjustment = adjustedIndent.length - prevLeaf.textLength
+ (prevLeaf as LeafPsiElement).rawReplaceWithText(adjustedIndent)
+ }
+ } else {
+ emit(child.startOffset, errorMessage(child), true)
+ if (autoCorrect) {
+ paramInnerIndentAdjustment = intendedIndent.length - child.psi.column
+ node.addChild(PsiWhiteSpaceImpl(intendedIndent), child)
+ }
+ }
+ if (paramInnerIndentAdjustment != 0 &&
+ child.elementType == KtStubElementTypes.VALUE_PARAMETER) {
+ child.visit { n ->
+ if (n.elementType == KtTokens.WHITE_SPACE && n.textContains('\n')) {
+ val split = n.text.split("\n")
+ (n.psi as LeafElement).rawReplaceWithText(split.joinToString("\n") {
+ if (paramInnerIndentAdjustment > 0) {
+ it + " ".repeat(paramInnerIndentAdjustment)
+ } else {
+ it.substring(0, Math.max(it.length + paramInnerIndentAdjustment, 0))
+ }
+ })
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ 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 errorMessage(node: ASTNode) =
+ when (node.elementType) {
+ KtTokens.LPAR -> """Unnecessary newline before "(""""
+ 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/SpacingAroundColonRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundColonRule.kt
index bb9aca83..e2d6b6a7 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundColonRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundColonRule.kt
@@ -14,8 +14,11 @@ import org.jetbrains.kotlin.psi.KtTypeParameterList
class SpacingAroundColonRule : Rule("colon-spacing") {
- 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 is LeafPsiElement && node.textMatches(":") && !node.isPartOfString()) {
if (node.isPartOf(KtAnnotation::class) || node.isPartOf(KtAnnotationEntry::class)) {
// todo: enfore "no spacing"
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRule.kt
index 2ebca751..2be2935a 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRule.kt
@@ -6,16 +6,28 @@ import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
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.util.PsiTreeUtil
+import org.jetbrains.kotlin.psi.psiUtil.startOffset
class SpacingAroundCommaRule : Rule("comma-spacing") {
- override fun visit(node: ASTNode, autoCorrect: Boolean,
- emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
- if (node is LeafPsiElement && node.textMatches(",") && !node.isPartOfString() &&
- PsiTreeUtil.nextLeaf(node) !is PsiWhiteSpace) {
- emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true)
- if (autoCorrect) {
- node.rawInsertAfterMe(PsiWhiteSpaceImpl(" "))
+ override fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ) {
+ if (node is LeafPsiElement && node.textMatches(",") && !node.isPartOfString()) {
+ val prevLeaf = PsiTreeUtil.prevLeaf(node, true)
+ if (prevLeaf is PsiWhiteSpace) {
+ emit(prevLeaf.startOffset, "Unexpected spacing before \"${node.text}\"", true)
+ if (autoCorrect) {
+ prevLeaf.node.treeParent.removeChild(prevLeaf.node)
+ }
+ }
+ if (PsiTreeUtil.nextLeaf(node) !is PsiWhiteSpace) {
+ emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true)
+ if (autoCorrect) {
+ node.rawInsertAfterMe(PsiWhiteSpaceImpl(" "))
+ }
}
}
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRule.kt
index 2f6d226b..2bce695e 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRule.kt
@@ -13,20 +13,25 @@ import org.jetbrains.kotlin.psi.KtLambdaExpression
class SpacingAroundCurlyRule : Rule("curly-spacing") {
- 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 is LeafPsiElement && !node.isPartOfString()) {
val prevLeaf = PsiTreeUtil.prevLeaf(node, true)
val nextLeaf = PsiTreeUtil.nextLeaf(node, true)
val spacingBefore: Boolean
val spacingAfter: Boolean
if (node.textMatches("{")) {
- spacingBefore = prevLeaf is PsiWhiteSpace || (prevLeaf?.node?.elementType == KtTokens.LPAR &&
+ spacingBefore = prevLeaf is PsiWhiteSpace || prevLeaf?.node?.elementType == KtTokens.AT || (prevLeaf?.node?.elementType == KtTokens.LPAR &&
(node.parent is KtLambdaExpression || node.parent.parent is KtLambdaExpression))
spacingAfter = nextLeaf is PsiWhiteSpace || nextLeaf?.node?.elementType == KtTokens.RBRACE
if (prevLeaf is PsiWhiteSpace &&
- !prevLeaf.textContains('\n') &&
- PsiTreeUtil.prevLeaf(prevLeaf, true)?.node?.elementType == KtTokens.LPAR) {
+ !prevLeaf.textContains('\n') &&
+ PsiTreeUtil.prevLeaf(prevLeaf, true)?.node?.let {
+ it.elementType == KtTokens.LPAR || it.elementType == KtTokens.AT
+ } == true) {
emit(node.startOffset, "Unexpected space before \"${node.text}\"", true)
if (autoCorrect) {
prevLeaf.node.treeParent.removeChild(prevLeaf.node)
@@ -41,11 +46,10 @@ class SpacingAroundCurlyRule : Rule("curly-spacing") {
node.parent.node.elementType == KtNodeTypes.CLASS_BODY)) {
emit(node.startOffset, "Unexpected newline before \"${node.text}\"", true)
if (autoCorrect) {
- (prevLeaf.node as LeafPsiElement).replaceWithText(" ")
+ (prevLeaf.node as LeafPsiElement).rawReplaceWithText(" ")
}
}
- } else
- if (node.textMatches("}")) {
+ } else if (node.textMatches("}")) {
spacingBefore = prevLeaf is PsiWhiteSpace || prevLeaf?.node?.elementType == KtTokens.LBRACE
spacingAfter = nextLeaf == null || nextLeaf is PsiWhiteSpace || shouldNotToBeSeparatedBySpace(nextLeaf)
if (nextLeaf is PsiWhiteSpace && !nextLeaf.textContains('\n') &&
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRule.kt
index 15bb5597..ec31bf9b 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRule.kt
@@ -29,8 +29,11 @@ class SpacingAroundKeywordRule : Rule("keyword-spacing") {
private val keywordsWithoutSpaces = TokenSet.create(KtTokens.GET_KEYWORD, KtTokens.SET_KEYWORD)
- 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 is LeafPsiElement) {
if (tokenSet.contains(node.elementType) && node.nextLeaf() !is PsiWhiteSpace) {
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRule.kt
index 4bc9448a..06dd72ee 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRule.kt
@@ -36,24 +36,36 @@ import org.jetbrains.kotlin.psi.KtSuperExpression
import org.jetbrains.kotlin.psi.KtTypeArgumentList
import org.jetbrains.kotlin.psi.KtTypeParameterList
import org.jetbrains.kotlin.psi.KtValueArgument
+import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
+import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
class SpacingAroundOperatorsRule : Rule("op-spacing") {
private val tokenSet = TokenSet.create(MUL, PLUS, MINUS, DIV, PERC, LT, GT, LTEQ, GTEQ, EQEQEQ, EXCLEQEQEQ, EQEQ,
EXCLEQ, ANDAND, OROR, ELVIS, EQ, MULTEQ, DIVEQ, PERCEQ, PLUSEQ, MINUSEQ, ARROW)
- 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 (tokenSet.contains(node.elementType) && node is LeafPsiElement &&
!node.isPartOf(KtPrefixExpression::class) && // not unary
- !node.isPartOf(KtTypeParameterList::class) && // fun <T>fn(): T {}
!node.isPartOf(KtTypeArgumentList::class) && // C<T>
- !node.isPartOf(KtValueArgument::class) && // fn(*array)
+ !(node.elementType == MUL && node.isPartOf(KtValueArgument::class)) && // fn(*array)
!node.isPartOf(KtImportDirective::class) && // import *
!node.isPartOf(KtSuperExpression::class) // super<T>
) {
- val spacingBefore = PsiTreeUtil.prevLeaf(node, true) is PsiWhiteSpace
- val spacingAfter = PsiTreeUtil.nextLeaf(node, true) is PsiWhiteSpace
+ if ((node.elementType == GT || node.elementType == LT) &&
+ // fun <T>fn(): T {}
+ node.getNonStrictParentOfType(KtTypeParameterList::class.java)?.parent?.node?.elementType !=
+ KtStubElementTypes.FUNCTION) {
+ return
+ }
+ val spacingBefore = PsiTreeUtil.prevLeaf(node, true) is PsiWhiteSpace ||
+ node.elementType == GT
+ val spacingAfter = PsiTreeUtil.nextLeaf(node, true) is PsiWhiteSpace ||
+ node.elementType == LT
when {
!spacingBefore && !spacingAfter -> {
emit(node.startOffset, "Missing spacing around \"${node.text}\"", true)
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRule.kt
new file mode 100644
index 00000000..d7bedbea
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRule.kt
@@ -0,0 +1,42 @@
+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.PsiWhiteSpace
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.lexer.KtTokens
+
+class SpacingAroundRangeOperatorRule : Rule("range-spacing") {
+
+ override fun visit(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+ ) {
+ if (node.elementType == KtTokens.RANGE) {
+ val prevLeaf = PsiTreeUtil.prevLeaf(node.psi, true)
+ val nextLeaf = PsiTreeUtil.nextLeaf(node.psi, true)
+ when {
+ prevLeaf is PsiWhiteSpace && nextLeaf is PsiWhiteSpace -> {
+ emit(node.startOffset, "Unexpected spacing around \"..\"", true)
+ if (autoCorrect) {
+ prevLeaf.node.treeParent.removeChild(prevLeaf.node)
+ nextLeaf.node.treeParent.removeChild(nextLeaf.node)
+ }
+ }
+ prevLeaf is PsiWhiteSpace -> {
+ emit(prevLeaf.node.startOffset, "Unexpected spacing before \"..\"", true)
+ if (autoCorrect) {
+ prevLeaf.node.treeParent.removeChild(prevLeaf.node)
+ }
+ }
+ nextLeaf is PsiWhiteSpace -> {
+ emit(nextLeaf.node.startOffset, "Unexpected spacing after \"..\"", true)
+ if (autoCorrect) {
+ nextLeaf.node.treeParent.removeChild(nextLeaf.node)
+ }
+ }
+ }
+ }
+ }
+}
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 9fd9190c..87f06b42 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,9 @@ import com.github.shyiko.ktlint.core.RuleSetProvider
class StandardRuleSetProvider : RuleSetProvider {
override fun get(): RuleSet = RuleSet("standard",
+ ChainWrappingRule(),
+ CommentSpacingRule(),
+ FilenameRule(),
FinalNewlineRule(),
// disabled until it's clear how to reconcile difference in Intellij & Android Studio import layout
// ImportOrderingRule(),
@@ -17,17 +20,21 @@ class StandardRuleSetProvider : RuleSetProvider {
NoEmptyClassBodyRule(),
// disabled until it's clear what to do in case of `import _.it`
// NoItParamInMultilineLambdaRule(),
+ NoLineBreakAfterElseRule(),
+ NoLineBreakBeforeAssignmentRule(),
NoMultipleSpacesRule(),
NoSemicolonsRule(),
NoTrailingSpacesRule(),
NoUnitReturnRule(),
NoUnusedImportsRule(),
NoWildcardImportsRule(),
+ ParameterListWrappingRule(),
SpacingAroundColonRule(),
SpacingAroundCommaRule(),
SpacingAroundCurlyRule(),
SpacingAroundKeywordRule(),
SpacingAroundOperatorsRule(),
+ SpacingAroundRangeOperatorRule(),
StringTemplateRule()
)
}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StringTemplateRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StringTemplateRule.kt
index f58a4bfe..e63d7927 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StringTemplateRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StringTemplateRule.kt
@@ -32,26 +32,27 @@ class StringTemplateRule : Rule("string-template") {
if (dotQualifiedExpression?.node?.elementType == KtStubElementTypes.DOT_QUALIFIED_EXPRESSION) {
val callExpression = dotQualifiedExpression!!.lastChild
val dot = callExpression.prevSibling
- if (dot.node.elementType == KtTokens.DOT && callExpression.text == "toString()" &&
- dotQualifiedExpression.firstChild.node.elementType != KtNodeTypes.SUPER_EXPRESSION) {
- emit(dot.node.startOffset, "Redundant 'toString()' call in string template", true)
+ if (dot?.node?.elementType == KtTokens.DOT &&
+ callExpression.text == "toString()" &&
+ dotQualifiedExpression.firstChild?.node?.elementType != KtNodeTypes.SUPER_EXPRESSION) {
+ emit(dot.node.startOffset, "Redundant \"toString()\" call in string template", true)
if (autoCorrect) {
node.removeChild(dot.node)
node.removeChild(callExpression.node)
}
}
}
- }
- if (elementType == KtNodeTypes.LONG_STRING_TEMPLATE_ENTRY &&
- node.text.let { it.substring(2, it.length - 1) }.all { it.isPartOfIdentifier() } &&
- (node.treeNext.elementType == KtTokens.CLOSING_QUOTE ||
- (node.psi.nextSibling.node.elementType == KtNodeTypes.LITERAL_STRING_TEMPLATE_ENTRY &&
- !node.psi.nextSibling.text[0].isPartOfIdentifier()))) {
- emit(node.treePrev.startOffset + 2, "Redundant curly braces", true)
- if (autoCorrect) {
- // fixme: a proper way would be to downcast to SHORT_STRING_TEMPLATE_ENTRY
- (node.psi.firstChild as LeafPsiElement).rawReplaceWithText("$") // entry start
- (node.psi.lastChild as LeafPsiElement).rawReplaceWithText("") // entry end
+ if (node.text.startsWith("${'$'}{") &&
+ node.text.let { it.substring(2, it.length - 1) }.all { it.isPartOfIdentifier() } &&
+ (node.treeNext.elementType == KtTokens.CLOSING_QUOTE ||
+ (node.psi.nextSibling.node.elementType == KtNodeTypes.LITERAL_STRING_TEMPLATE_ENTRY &&
+ !node.psi.nextSibling.text[0].isPartOfIdentifier()))) {
+ emit(node.treePrev.startOffset + 2, "Redundant curly braces", true)
+ if (autoCorrect) {
+ // fixme: a proper way would be to downcast to SHORT_STRING_TEMPLATE_ENTRY
+ (node.psi.firstChild as LeafPsiElement).rawReplaceWithText("$") // entry start
+ (node.psi.lastChild as LeafPsiElement).rawReplaceWithText("") // entry end
+ }
}
}
}
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 2acc5c2d..d9e03b1e 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,20 @@
package com.github.shyiko.ktlint.ruleset.standard
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
+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(): PsiElement? = PsiTreeUtil.prevLeaf(this)
+internal fun PsiElement.nextLeaf(): PsiElement? = PsiTreeUtil.nextLeaf(this)
+internal fun ASTNode.visit(cb: (node: ASTNode) -> Unit) {
+ cb(this)
+ this.getChildren(null).forEach { it.visit(cb) }
+}
-internal fun <T>List<T>.head() = this.subList(0, this.size - 1)
-internal fun <T>List<T>.tail() = this.subList(1, this.size)
+internal fun <T> List<T>.head() = this.subList(0, this.size - 1)
+internal fun <T> List<T>.tail() = this.subList(1, this.size)