1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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 ""
}
}
|