@@ -8,6 +8,7 @@ import com.pinterest.ktlint.core.ast.ElementType.BLOCK
8
8
import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
9
9
import com.pinterest.ktlint.core.ast.ElementType.CALLABLE_REFERENCE_EXPRESSION
10
10
import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION
11
+ import com.pinterest.ktlint.core.ast.ElementType.CLASS
11
12
import com.pinterest.ktlint.core.ast.ElementType.COLON
12
13
import com.pinterest.ktlint.core.ast.ElementType.COLONCOLON
13
14
import com.pinterest.ktlint.core.ast.ElementType.COMMA
@@ -21,6 +22,8 @@ import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
21
22
import com.pinterest.ktlint.core.ast.ElementType.EQ
22
23
import com.pinterest.ktlint.core.ast.ElementType.FUN
23
24
import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_LITERAL
25
+ import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_TYPE
26
+ import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_TYPE_RECEIVER
24
27
import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
25
28
import com.pinterest.ktlint.core.ast.ElementType.IF
26
29
import com.pinterest.ktlint.core.ast.ElementType.IMPORT_DIRECTIVE
@@ -37,46 +40,44 @@ import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
37
40
import com.pinterest.ktlint.core.ast.ElementType.PLUS
38
41
import com.pinterest.ktlint.core.ast.ElementType.PLUSEQ
39
42
import com.pinterest.ktlint.core.ast.ElementType.PRIMARY_CONSTRUCTOR
40
- import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
41
43
import com.pinterest.ktlint.core.ast.ElementType.RETURN
42
44
import com.pinterest.ktlint.core.ast.ElementType.RETURN_KEYWORD
43
45
import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS
44
46
import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS_EXPRESSION
45
47
import com.pinterest.ktlint.core.ast.ElementType.SECONDARY_CONSTRUCTOR
46
48
import com.pinterest.ktlint.core.ast.ElementType.SEMICOLON
47
- import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT
49
+ import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST
48
50
import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST
51
+ import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER
49
52
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST
50
53
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
51
- import com.pinterest.ktlint.core.ast.isLeaf
52
54
import com.pinterest.ktlint.core.ast.nextCodeSibling
53
55
import com.pinterest.ktlint.core.ast.parent
54
56
import com.pinterest.ktlint.core.ast.prevCodeSibling
57
+ import org.cqfn.diktat.common.config.rules.RuleConfiguration
55
58
import org.cqfn.diktat.common.config.rules.RulesConfig
59
+ import org.cqfn.diktat.common.config.rules.getRuleConfig
56
60
import org.cqfn.diktat.ruleset.constants.Warnings.REDUNDANT_SEMICOLON
57
61
import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_NEWLINES
58
62
import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace
59
63
import org.cqfn.diktat.ruleset.utils.emptyBlockList
60
64
import org.cqfn.diktat.ruleset.utils.extractLineOfText
61
65
import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType
66
+ import org.cqfn.diktat.ruleset.utils.getIdentifierName
62
67
import org.cqfn.diktat.ruleset.utils.isBeginByNewline
63
68
import org.cqfn.diktat.ruleset.utils.isEol
64
69
import org.cqfn.diktat.ruleset.utils.isFollowedByNewline
65
70
import org.cqfn.diktat.ruleset.utils.isSingleLineIfElse
66
71
import org.cqfn.diktat.ruleset.utils.leaveOnlyOneNewLine
72
+ import org.cqfn.diktat.ruleset.utils.log
73
+ import org.jetbrains.kotlin.backend.common.onlyIf
67
74
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
68
75
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
69
76
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
70
77
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
71
- import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType
72
78
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
73
- import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
74
- import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
75
- import org.jetbrains.kotlin.psi.KtExpression
76
- import org.jetbrains.kotlin.psi.KtExpressionImpl
77
- import org.jetbrains.kotlin.psi.KtPostfixExpression
78
- import org.jetbrains.kotlin.psi.KtQualifiedExpression
79
- import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression
79
+ import org.jetbrains.kotlin.psi.KtParameterList
80
+ import org.jetbrains.kotlin.psi.KtSuperTypeList
80
81
import org.jetbrains.kotlin.psi.psiUtil.children
81
82
import org.jetbrains.kotlin.psi.psiUtil.parents
82
83
import org.jetbrains.kotlin.psi.psiUtil.siblings
@@ -91,6 +92,7 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings
91
92
* 6. Ensures that function or constructor name isn't separated from `(` by space or newline
92
93
* 7. Ensures that in multiline lambda newline follows arrow or, in case of lambda without explicit parameters, opening brace
93
94
* 8. Checks that functions with single `return` are simplified to functions with expression body
95
+ * 9. parameter or argument lists and supertype lists that have more than 2 elements should be separated by newlines
94
96
*/
95
97
@Suppress(" ForbiddenComment" )
96
98
class NewlinesRule (private val configRules : List <RulesConfig >) : Rule(" newlines" ) {
@@ -107,8 +109,11 @@ class NewlinesRule(private val configRules: List<RulesConfig>) : Rule("newlines"
107
109
private val dropChainValues = TokenSet .create(EOL_COMMENT , WHITE_SPACE , BLOCK_COMMENT , KDOC )
108
110
}
109
111
110
- private lateinit var emitWarn: ((offset: Int , errorMessage: String , canBeAutoCorrected: Boolean ) -> Unit )
112
+ private val configuration by lazy {
113
+ NewlinesRuleConfiguration (configRules.getRuleConfig(WRONG_NEWLINES )?.configuration ? : emptyMap())
114
+ }
111
115
private var isFixMode: Boolean = false
116
+ private lateinit var emitWarn: ((offset: Int , errorMessage: String , canBeAutoCorrected: Boolean ) -> Unit )
112
117
113
118
override fun visit (node : ASTNode ,
114
119
autoCorrect : Boolean ,
@@ -124,9 +129,13 @@ class NewlinesRule(private val configRules: List<RulesConfig>) : Rule("newlines"
124
129
COMMA -> handleComma(node)
125
130
BLOCK -> handleLambdaBody(node)
126
131
RETURN -> handleReturnStatement(node)
132
+ SUPER_TYPE_LIST , VALUE_PARAMETER_LIST -> handleList(node)
127
133
}
128
134
}
129
135
136
+ /* *
137
+ * Check that EOL semicolon is used only in enums
138
+ */
130
139
private fun handleSemicolon (node : ASTNode ) {
131
140
if (node.isEol() && node.treeParent.elementType != ENUM_ENTRY ) {
132
141
// semicolon at the end of line which is not part of enum members declarations
@@ -213,6 +222,9 @@ class NewlinesRule(private val configRules: List<RulesConfig>) : Rule("newlines"
213
222
}
214
223
}
215
224
225
+ /* *
226
+ * Check that newline is not placed before a comma
227
+ */
216
228
private fun handleComma (node : ASTNode ) {
217
229
val prevNewLine = node
218
230
.parent({ it.treePrev != null }, strict = false )
@@ -296,6 +308,69 @@ class NewlinesRule(private val configRules: List<RulesConfig>) : Rule("newlines"
296
308
}
297
309
}
298
310
311
+ /* *
312
+ * Checks that members of [VALUE_PARAMETER_LIST] (list of function parameters at declaration site) are separated with newlines.
313
+ * Also checks that entries of [SUPER_TYPE_LIST] are separated by newlines.
314
+ */
315
+ private fun handleList (node : ASTNode ) {
316
+ if (node.elementType == VALUE_PARAMETER_LIST && node.treeParent.elementType.let { it == FUNCTION_TYPE || it == FUNCTION_TYPE_RECEIVER }) {
317
+ // do not check other value lists
318
+ return
319
+ }
320
+
321
+ val (numEntries, entryType) = when (node.elementType) {
322
+ VALUE_PARAMETER_LIST -> (node.psi as KtParameterList ).parameters.size to " value parameters"
323
+ SUPER_TYPE_LIST -> (node.psi as KtSuperTypeList ).entries.size to " supertype list entries"
324
+ else -> {
325
+ log.warn(" Unexpected node element type ${node.elementType} " )
326
+ return
327
+ }
328
+ }
329
+ if (numEntries > configuration.maxParametersInOneLine) {
330
+ when (node.elementType) {
331
+ VALUE_PARAMETER_LIST -> handleFirstValueParameter(node)
332
+ }
333
+
334
+ handleValueParameterList(node, entryType)
335
+ }
336
+ }
337
+
338
+ private fun handleFirstValueParameter (node : ASTNode ) = node
339
+ .children()
340
+ .takeWhile { ! it.textContains(' \n ' ) }
341
+ .filter { it.elementType == VALUE_PARAMETER }
342
+ .toList()
343
+ .onlyIf({ size > 1 }) {
344
+ WRONG_NEWLINES .warnAndFix(configRules, emitWarn, isFixMode, " first parameter should be placed on a separate line " +
345
+ " or all other parameters should be aligned with it in declaration of <${node.getParentIdentifier()} >" , node.startOffset, node) {
346
+ node.appendNewlineMergingWhiteSpace(it.first().treePrev.takeIf { it.elementType == WHITE_SPACE }, it.first())
347
+ }
348
+ }
349
+
350
+ private fun handleValueParameterList (node : ASTNode , entryType : String ) = node
351
+ .children()
352
+ .filter {
353
+ it.elementType == COMMA &&
354
+ ! it.treeNext.run { elementType == WHITE_SPACE && textContains(' \n ' ) }
355
+ }
356
+ .toList()
357
+ .onlyIf({ isNotEmpty() }) { invalidCommas ->
358
+ WRONG_NEWLINES .warnAndFix(configRules, emitWarn, isFixMode,
359
+ " $entryType should be placed on different lines in declaration of <${node.getParentIdentifier()} >" , node.startOffset, node) {
360
+ invalidCommas.forEach { comma ->
361
+ val nextWhiteSpace = comma.treeNext.takeIf { it.elementType == WHITE_SPACE }
362
+ comma.appendNewlineMergingWhiteSpace(nextWhiteSpace, nextWhiteSpace?.treeNext ? : comma.treeNext)
363
+ }
364
+ }
365
+ }
366
+
367
+ @Suppress(" UnsafeCallOnNullableType" )
368
+ private fun ASTNode.getParentIdentifier () = when (treeParent.elementType) {
369
+ PRIMARY_CONSTRUCTOR -> treeParent.treeParent
370
+ SECONDARY_CONSTRUCTOR -> parent(CLASS )!!
371
+ else -> treeParent
372
+ }
373
+ .getIdentifierName()?.text
299
374
300
375
private fun ASTNode.getOrderedCallExpressions (psi : PsiElement , result : MutableList <ASTNode >) {
301
376
// if statements here have the only right order - don't change it
@@ -373,3 +448,10 @@ class NewlinesRule(private val configRules: List<RulesConfig>) : Rule("newlines"
373
448
firstChildNode.elementType == IDENTIFIER &&
374
449
treeParent.elementType == BINARY_EXPRESSION
375
450
}
451
+
452
+ private class NewlinesRuleConfiguration (config : Map <String , String >) : RuleConfiguration(config) {
453
+ /* *
454
+ * If the number of parameters on one line is more than this threshold, all parameters should be placed on separate lines.
455
+ */
456
+ val maxParametersInOneLine = config[" maxParametersInOneLine" ]?.toInt() ? : 2
457
+ }
0 commit comments